viernes, 27 de marzo de 2026
Previsualización de correo electrónico que no se ve en el cuerpo del mensaje
viernes, 18 de marzo de 2022
Powershell: buscar una cadena dentro de unos ficheros recursivamente
¿Qué ficheros dentro de este directorio y descendientes contienen una cadena específica?
gci -recurse *.php |sls button |select -first 10
jueves, 17 de marzo de 2022
Jugando a wordle (versión tildes) con powershell
Después de probar un par de palabras en el Wordle de ayer (en versión tildes), y no dar con la palabra correcta, me decidí a tener algo de "ayuda" (la palabra "trampas" es muy fea) a partir de un diccionario.
Dado que tengo un fichero que es un listado de palabras en español, con una palabra por línea, me gustaría aplicar un filtro que me busque las palabras que cumplen determinadas condiciones, como tener una "P" en primera posición, contener una "L" (sin especificar la posición) y que tengan una determinada longitud.
Esto me dará varias opciones (en el mejor de los casos, puede que sólo haya una palabra que cumpla esas condiciones, en cuyo caso esa sería la palabra buscada), y mi idea era jugar alguna de esas palabras. Por eso decía lo de "ayuda" en vez de "trampas", pero vamos, puede llamarse de cualquiera de las dos formas. Lo importante aquí es cómo se hace eso en Powershell.
Mi fichero con la lista de palabras en español se llama words.es.txt.
Wordle con tildes: https://wordle.danielfrg.com/tildes/
Después de probar PLÁTANO, las letras acertadas están en las posiciones 0 (P) y 2 (L).
Otras letras acertadas, aunque estarán en otras posiciones, son la A y la O.
Lo interesante aquí es fijarse en el filtro para poner condiciones, que se pone con el comando "Where-Object" seguido de una expresión entre {llaves}, aunque tiene un alias que me gusta, y es "?".
Así pues, la línea de comando powershell para listar las palabras que cumplen esas condiciones quedaría así:
gc words.es.txt |?{($_.length -eq 8) -and ($_[0] -eq 'p') -and ($_[2] -eq 'l')}|sls a |sls o
Curiosamente, he de filtrar por palabras de longitud 8, ya que palabras con letras especiales (eñes, tildes...) se convierten en dos letras, por ejemplo, "peldaño" y "policía" dan "peldaño" y "policÃa", convirtiendo palabras de 7 letras en 8.
Esto debe ser por tema de codificación diferente entre cmd.exe y powerhsell. Pero eso lo investigaré en otra ocasión.
jueves, 10 de febrero de 2022
Powershell: cómo saber qué procesos utilizan una DLL concreta
Ejecutar en la línea de comandos de Powershell:
tasklist /m msvcrt.dll
Una cosa interesante es que se puede pedir la salida en formato CSV, TABLE y LIST.
tasklist /m msvcrt.dll /fo csv > procesos.csv
viernes, 21 de agosto de 2020
Fecha actual, subselects y registros pasados más recientes en MariaDB
MARIADB: FECHA ACTUAL Y REGISTROS MÁS RECIENTES
Como me ha costado un rato, comparto esto por si otro estuviera en el mismo caso, lo ve más rápido y si no es exactamente lo que busca, igual le vale como punto de partida.
Tengo una base de datos en MariaDB y necesito sacar un registro con la fecha más reciente, por lo que he necesitado la fórmula curdate() para sacar la fecha actual
Como lo que quería era la factura más reciente, anterior a hoy (PROBLEMA: tenemos algunas con fecha futura), he hecho una subconsulta. Primero he seleccionado los datos que quería de la factura y he puesto que la fecha fuera igual a la mayor (más reciente) de las pasadas (en el WHERE de la subconsulta le indico que sea anterior a hoy).
SELECT id_factura, cliente, fecha
FROM facturas
WHERE fecha = (select max(fecha)
from facturas
where fecha < curdate())
Es sencillo, pero me ha dado un poco de quebradero de cabeza porque al usar MAX hay que poner el campo que queramos y el paréntesis pegado a la palabra porque si no da error, así me ha pasado a mí en la consulta SQL. Utilizo un servidor de Hostinger, no sé si este comportamiento será el mismo en todos, pero mejor coger una buena costumbre para evitar fallos similares.
Espero que os pueda servir.
martes, 9 de enero de 2018
Renumerar Secuencias en Oracle (ALTER SEQUENCE)
HY000 - 1 OCIStmtExecute: ORA-00001: restricción única (...) violada
Analizando la estructura de la tabla, veo que esta tiene como clave primaria un campo ID [NUMBER(7,0)], y que la operación de inserción obtiene el siguiente ID a insertar de una secuencia (CREATE SEQUENCE...). Esto me lleva a sospechar que probablemente el siguiente ID a insertar esté coincidiendo con algún ID ya existente en la tabla, cosa extraña tratándose de una secuencia, y que no debería pasar, pero... ¡tantas cosas no deberían pasar y pasan!
Vamos con las secuencias en ORACLE.
Para saber cuál será el siguiente valor que devolverá una secuencia, podemos hacer dos cosas:
1. Función NEXTVAL.
La instrucción
SELECT SEC_MI_SECUENCIA.nextval FROM dual;
=> 3601
nos devuelve el siguiente número de la secuencia. El único "problema" es que esta operación "consume" un valor de esa secuencia. Supongamos que me devuelve el valor 3601. Si vuelvo a ejecutar la operación, devolvería un nuevo valor, 3602 (asumiendo que la secuencia se incrementa en pasos de 1 unidad). Cada vez que se vuelva a ejecutar esa instrucción, estaré "gastando" un número de la secuencia. Así que, si este comportamiento no es deseable, cuidado. Esto puede ocurrir cuando tenemos tablas donde queremos TODOS los registros que se puedan ir insertando, como una tabla de registro de actividad (log), por ejemplo. Si vemos saltos en la secuencia de IDs, no sabemos si es que son "normales" o es que tal vez alguien ha eliminado registros para borrar las huellas de alguna operación.
SELECT SEC_MI_SECUENCIA.nextval FROM dual;
=> 3609
2. Función CURRVAL
Esta alternativa permite consultar el último valor que devolvió la secuencia, pero sin generar uno nuevo. Es decir, por muchas ejecuciones que hagamos, siempre nos va a devolver el mismo valor.select SEC_MI_SECUENCIA.currval from dual;
=> 3609
select SEC_MI_SECUENCIA.currval from dual;
=> 3609
Dicho esto, la solución a mi problema es fácil. Veo en los datos que hay registros con un ID ya coincidente con el próximo que generará la secuencia. Así que obtengo el máximo, que resulta ser
select max(id) from SEC_MI_SECUENCIA;
==> 8596
Así que voy a ALTERAR mi secuencia para que genere números a partir del 8600. Para ello, debo especificar un incremento igual a la diferencia entre el valor actual y el deseado. En mi caso, 8600 - 3609 = 4991. Así que hay que alterar la secuencia DOS VECES: una para poner un incremento de 4991, a continuación obtenemos el siguiente valor, y después volvemos a alterar la secuencia para poner el incremento en 1. Así:
ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 4991;
SELECT SEC_MI_SECUENCIA.nextval FROM dual;
ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 1;
Tras esto, ya hemos comprobado que la inserción no da problemas.
Hasta que alguien vuelva a tocar los datos...
jueves, 29 de septiembre de 2016
Recorrer una estructura de directorios descomprimiendo ficheros con clave
Tengo una copia de seguridad que es una réplica de una estructura de carpetas y subcarpetas, con varios ficheros repartidos por todo el árbol de directorios.La copia replica la estructura de carpetas en una unidad externa. Cada fichero se comprime individualmente y se cifra con una contraseña.
Si se trata de recuperar un fichero específico, no hay mucho problema, copio el comprimido a la carpeta de destino y con cualquier utilidad de descompresión lo extraigo manualmente, tecleando la contraseña en el momento necesario.
Pero si quiero restaurar TODA UNA CARPETA Y EL SUBÁRBOL que cuelga de ella, ir extrayendo los ficheros uno a uno no es una opción válida (son muchos), y deseo automatizar esta operación. Para ello, necesitaré una utilidad de línea de comandos que me permita extraer un archivo individual (usaré el 7za, la utilidad de línea de comandos del 7-zip) y un script que me permita ir recorriendo el árbol de directorios uno a uno y ejecutar esta utilidad en cada uno de ellos. También tendré que ir especificando la ruta destino individualmente para cada fichero, que deberé ir construyendo dinámicamente.
En esta ocasión, estoy trabajando en Windows. Tras un rato leyendo las especificaciones del comando FOR (en una ventana de comando basta con escribir "help for"), he aquí la solución al problema planteado:
set z="c:\bin\util\7za.exe"
set d="c:\ruta\destino"
set clave=",,"
for /D /R %%i in (*.* .) do %z% x -r -o%d%%%~pi%%~ni %%i\*.zip -p%clave% -y
::~ for /D /R %%i in (*.*) do @echo de %%~pi a %d%%%~pi%%~ni
No repetiré aquí las especificaciones del comando FOR, para eso está su ayuda, que considero suficiente (aunque un poco falta de claridad, lo reconozco), pero sí daré unas breves notas:
- El /D hace referencia a que trabajaremos con directorios, y /R es para que se haga un recorrido recursivo
- El comando a ejecutar con cada directorio debe ir en la misma línea que el FOR, hasta donde yo sé. Desconozco si se puede dividir la instrucción en varias líneas, lo cual ayudaría bastante a la legibilidad, pero yo no lo he conseguido.
- Las variables de entorno (establecidas en las primeras líneas con SET) se escriben como %VAR% y las variables del FOR llevan un doble símbolo de porcentaje %%VAR por estar dentro de un fichero de script. Si estuviéramos tecleándolas directamente en una ventana de comando se escribiría un único %, así: %VAR.
- Los modificadores introducidos delante del nombre de la variable (en el comando a ejecutar) sirven para extraer parte del nombre del directorio, así ~p significa PATH (sólo la ruta, sin el nombre) ~n significa NOMBRE (nombre del directorio a procesar)
- La línea comentada (la que comienza por ::~ for) se puede utilizar para testear el resultado antes de ejecutarlo de verdad, imprimiendo las rutas a procesar, pero sin ejecutar la operación. Para probarlo, bastaría con descomentar esta línea y comentar la anterior a ella.
En la cláusula in es importante añadir, además de *.*, el punto (.) que hace referencia al directorio actual. Si no se incluye, se procesan todos los directorios que cuelgan del actual, pero no el directorio contenedor. Tampoco pasa nada, siempre podía haber copiado la carpeta a restaurar como única carpeta hija de c:\tmp, pongamos por caso, y ejecutar el script en c:\tmp directamente. Pero incluyendo el punto la solución es más genérica, pues vale tanto si en el directorio actual hay ficheros o si sólo hay directorios.
Como reflexión, pensemos que cuando hacemos una copia de seguridad, no basta con tener los ficheros guardados, sino que debemos tener previsto (¡y probado!) el sistema que utilizaremos para restaurar la información en caso necesario. De nada nos sirve tener una copia de seguridad si, en caso de necesidad, a la hora de restaurarla nos vamos a tener que esperar una eternidad a que la información vuelva a estar disponible, por tener que llevar a cabo un proceso manual o un proceso que, aunque esté automatizado, no responda en un tiempo razonable.
Había una cita conocida que no recuerdo literalmente, pero venía a decir una cosa importantísima, algo así como:
- "No planifiques una estrategia de copias de seguridad, planifica una estrategia de recuperación"
(si alguien sabe el autor y la frase exacta, le estaré agradecido si me lo pone en un comentario).
Ahora es buen momento para probar tu procedimiento de contingencia: ¿cuánto tardarías si tuvieras que restaurar ahora mismo alguna información importante?
jueves, 28 de julio de 2016
La crème de la crème (consultas TOP-N en Oracle)
Pero la cosa cambia un poco si las queremos ordenadas, es decir los mejores resultados, las mejores notas, los mejores salarios, la crème de la crème... (o los peores).
Esto significa sacar las N filas primeras (o mínimas) o las N filas últimas (o máximas), lo que se denomina a veces consultas TOP-N (TOP-N queries, por si queréis buscarlo en inglés).
En el caso de SQLite3 podíamos poner la cláusula LIMIT después del ORDER BY, como se explicaba en aquella entrada. Pero en ORACLE no existe la cláusula LIMIT, así que habrá que hacerlo de alguna otra manera.
En principio, utilizando la columna "mágica" ROWNUM, si hacemos lo siguiente:
SELECT * FROM TABLA WHERE ROWNUM <= 100
estamos obteniendo 100 filas aleatorias. Uno podría pensar que entonces, para obtener las TOP-100 filas bastaría con añadir una condición ORDER BY, tal que así:
SELECT * FROM TABLA WHERE ROWNUM <= 100 ORDER BY CAMPO
Pero eso no funcionaría. Lo que nos estaría dando son 100 filas aleatorias, y una vez seleccionadas (y no antes), nos las mostraría ordenadas por el CAMPO indicado. Pero eso no significa que nos esté dando las 100 filas PRIMERAS (según en el orden indicado) de la tabla. El problema es que el valor de ROWNUM se está asignando justo al seleccionar la fila, y la operación ORDER BY se aplica después.
Así pues, la forma correcta de hacerlo en Oracle es intercalar una SELECT dentro de otra:
SELECT * FROM (la-consulta-original) WHERE ROWNUM <= 100
Es decir, para el caso que nos ocupa:
SELECT * FROM (SELECT * FROM TABLA ORDER BY CAMPO) WHERE ROWNUM <= 100
Ale, problema resuelto. A seguir tecleando.
--
Nota: no confundir ROWNUM con ROWID. Ambas son columnas que asigna ORACLE automáticamente, pero no significan lo mismo.
Si queréis una explicación muy buena y bastante detallada acerca de la diferencia entre ROWNUM y ROWID y cómo utilizar ROWNUM para consultas TOP-N y para resultados paginados, echadle un vistazo a este artículo
http://www.epidataconsulting.com/tikiwiki/tiki-read_article.php?articleId=63
martes, 19 de julio de 2016
Codificación de caracteres (charset) en una BD Oracle
SELECT * FROM NLS_DATABASE_PARAMETERS;
podremos saber no sólo la codificación, sino otros parámetros interesantes como el idioma, los separadores decimales y de miles, el formato de fecha y hora, la versión de la BD, el símbolo de moneda y algunas cosas más...
jueves, 23 de junio de 2016
Sesión de galletitas y galletitas de sesiones
1. Sesiones
El servidor web guardará la información de la sesión en algún sitio, puede ser en ficheros de texto, binarios, en una base de datos... Normalmente, una configuración por defecto de Apache + PHP implica que se guarden en ficheros de texto plano.
Vale. Hasta aquí bien.
2. Cookies
Los servidores pueden usar así esta información para "recordar" las preferencias del usuario, su login automático y un sinfín de cosas más útiles y beneficiosas para el usuario, y algunas otras que no son tan beneficiosas para el usuario (pero sí para ciertos sitios web, especialmente si venden publicidad o si tienen intenciones algo más siniestras), por ejemplo, para rastrear las preferencias y hábitos de navegación.
Los navegadores o, lo que es lo mismo, los usuarios, sabemos que si nos hacemos los estrechos la página no nos va a dar mucha funcionalidad, así que nos ponemos en modo facilón, nos dejamos querer y accedemos fácilmente a que el servidor web nos
El navegador guarda ese paquetito de información, esa cookie, en un fichero pequeño normalmente de texto (también pueden ser binarios) o, algo que se ve cada vez más en las últimas versiones de los navegadores, en bases de datos locales tipo SQLite o similares. En cualquier caso, ficheros en el ordenador del cliente web. O, al menos esto es así en teoría, ya que aquí me surge el primer misterio que mencionaba en la introducción. Pero antes de abordar el misterio, recapitulemos lo que llevamos dicho hasta ahora.
Resumiendo:
- Cookies: pequeños trozos de información que almacena el navegador en ficheros de texto, binarios o BD locales tipo SQLite. Se pueden usar para muchas cosas, una de ellas para almacenar ID de sesión (pero también para rastrear al usuario, conocer preferencias...)
- Sesiones: interacciones entre navegador y servidor web que recuerdan info (estado) del usuario, normalmente usando cookies. La información se almacena en el servidor, ya sea en ficheros de texto (pares nombre:valor), binarios, bases de datos...
3. Vale. ¿Y dónde se almacenan?
3.1 LAS COOKIES
https://support.mozilla.org/es/kb/perfiles-el-lugar-donde-firefox-almacena-tus-contr
que se encuentra dentro de la carpeta %AppData%\Roaming\Mozilla\Firefox\Profiles
Si sólo tienes un perfil de usuario, éste incluye la palabra 'default' en el nombre de la carpeta. En mi caso:
%AppData%\Roaming\Mozilla\Firefox\Profiles\67bl3kpn.default-1443683694326
y, dentro de esta carpeta, hay un archivo cookies.sqlite

El contenido de este archivo se puede ver con cualquier visualizador de ficheros SQLite. A mí me gusta mucho el SQLite Expert (edición Personal). Más adelante le echaremos un vistazo.
En el caso de Chrome, la ruta donde se almacena la BD de cookies es
%LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies
y el fichero se llama cookies a secas, sin extensión. También lo podemos abrir con SQLite Expert o cualquier otro visor.
Si echamos un vistazo a ambos ficheros, veremos cosas parecidas a esto en el caso de Firefox

y esto en el caso de Chrome
3.2 LAS SESIONES
Si miramos en esa carpeta, veremos las sesiones que se han ido creando en los últimos días:

4. ¿Me puedes poner un ejemplo?
Página 1
01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones - Página 1</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />'
15 . 'Nombre: ' . session_name() . '<br />';
16
17 //almacenamos algún dato en esa sesión
18 $_SESSION['mes'] = 'junio';
19 $_SESSION['usuario'] = 'pepe';
20 $_SESSION['flor'] = 'amapola';
21
22 echo '<pre>';
23 print_r($_SESSION);
24 echo '</pre>';
25 echo "<br>Ir a <a href='pagina2.php'>la página 2</a> a consultar las variables de sesión"
26
27 ?>
28
29 </body>
30 </html>
Y esta es la página 2
01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />Nombre: ' . session_name() . '<br />';
15
16 //recuperamos los valores almacenados en la sesión
17 echo 'Recuperamos el valor mes=' . $_SESSION['mes'] . '<br />';
18 echo 'Recuperamos el valor usuario=' . $_SESSION['usuario'] . '<br />';
19 echo 'Recuperamos el valor flor=' . $_SESSION['flor'] . '<br />';
20
21 echo '<pre>';
22 print_r($_SESSION);
23 echo '</pre>';
24 ?>
25 </body>
26 </html>
Abramos en el navegador la página 1
Antes de pasar a la página 2, veamos qué ha pasado en los sistemas de archivos del servidor y del cliente.
En el servidor
como se ve, el nombre del archivo coincide con el session_id que hemos visto en la página. Si abrimos ese fichero, su contenido es:
Hmmm... Interesante. Vemos que junto a cada variable aparece un tipo de datos (s=string), su tamaño (5) y su valor ("junio"). Las distintas variables de sesión se separan con pipelines.
Pero es a la hora de ver qué ha pasado con las cookies en el cliente cuando aparece el primer "misterio". En teoría, las cookies se almacenan en la BD SQLite que hemos dicho. Pero, cuando muestro las cookies ordenadas por fecha de acceso, no encuentro la cookie de nombre PHPSESSID, que es el nombre que me devuelve la función session_name(). Esto es lo que consigo ver cuando accedo a la BD y filtro por el dominio 'localhost':
Como tras muchos intentos no he conseguido encontrar esta información en la BD, al final he instalado el plugin Firebug en Firefox, que permite visualizar las cookies y ahí sí que puedo ver la cookie que me interesa, como se puede ver un poco más arriba en la figura 6. También si abro la página 2 con el Firebug activado (F12) puedo ver que la cookie de sesión PHPSESSID es la misma
Aún no he conseguido averiguar dónde se almacena la cookie PHPSESSID, pero es obvio que el navegador la está guardando en algún sitio y a mí me gustaría saber dónde. Vale que con el Firebug puedo inspeccionar la cookie, lo cual me soluciona la papeleta por el momento, pero me quedo un poco mosca con no haber podido encontrar la ubicación exacta de la cookie en el disco.
¿Alguien tiene alguna idea?
martes, 10 de mayo de 2016
Estilos CSS alternativos
Veamos cómo hacer esto.
Definiremos dos hojas, estilo1.css y estilo2.css con los siguientes contenidos:
Fichero 1
/* estilo 1, normal */
body {
font-family: verdana,helvetica;
color: #0000FF; /* texto azul */
background-color: #f8e8a0; /* fondo amarillo */
font-size: 16px;
}
Fichero 2
/* estilo 2, alto contraste, letras grandes */
body {
font-family: verdana,helvetica;
color: #ffffff; /* texto blanco */
background-color: #000000; /* fondo negro */
font-size: 36px;
}
Y ahora hacemos nuestra página, tal que así
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>sin título</title>
<link href="estilo1.css" rel="stylesheet" type="text/css" title="Estilo 1">
<link href="estilo2.css" rel="stylesheet" type="text/css" title="Estilo 2">
</head>
<body>
<h1>Elija una de las siguientes opciones</h1>
<ol>
<li>Opción primera</li>
<li>Opción segunda</li>
<li>Opción tercera</li>
</ol>
</body>
</html>
Si abrimos ahora la página en el navegador, la veremos con el primer estilo aplicado:

Y en el menú Ver / Estilos podemos elegir cambiar a otro estilo
Y veríamos el resultado que hemos definido de alto contraste y letras grandes, tal que así:
ALGUNAS EXPLICACIONES
En los atributos "title" hemos puesto los valores "Estilo 1" y "Estilo 2". Si no hubiéramos definido el atributo "title" en alguno de los estilos esto significaría que el estilo es permanente. Esto significa que se aplica siempre, independientemente de otros estilos que se eligieran por el usuario. Si hay enlaces a varias páginas CSS sin "title", se aplicarían todas ellas.
Al incluir el atributo "title", lo que estamos haciendo es indicar que esos estilos son preferidos. Esto significa que se aplican por defecto (en nuestro caso, el "Estilo 1"), pero si el usuario selecciona otra(s) página(s) con título diferente, dejarían de aplicarse las primeras y pasarían a aplicarse todas las que tuvieran el otro estilo (en nuestro caso, el "Estilo 2"). Si dos o más páginas tienen el mismo título, entonces se aplican todas ellas al elegir el estilo correspondiente en el menú Ver del navegador.
Normalmente, hasta aquí suele ser suficiente. Pero el estándar HTML también define otro valor posible para el atributo "rel"
rel="alternate stylesheet"
Aunque no todos los navegadores implementan esto. Básicamente, está indicando que hay otros estilos alternativos, pero, al contrario que los estilos preferidos (que se aplican por defecto, al menos el primero de ellos), los estilos alternativos no se aplican por defecto, sino sólo en el caso de que el usuario lo seleccione explícitamente.
La verdad es que con utilizar estilos con títulos ya tendríamos un sistema de estilos alternativos, así que en general esta etiqueta me parece innecesaria para solucionar situaciones como la planteada al principio. Seguramente pueda idearse algún caso donde la diferencia sea significativa, pero en general no lo considero algo crítico, al menos para casos típicos.
Si queréis unas explicaciones más detalladas, echadle un vistazo a esta página, que está muy bien explicado todo esto.
viernes, 15 de abril de 2016
To REST or not to REST
Aquí va mi resumen personal para leer en cinco minutos, al que volveré dentro de un tiempo cuando se me haya olvidado, y espero que me sirva de refresco. Quien quiera más detalles, abajo encontrará algunos enlaces. Básicamente, lo que resumo aquí se basa en un 70-80% en el primer enlace, que es el que más útil me ha parecido de los que he encontrado hasta ahora.
Imagen de Softeis - Trabajo propio, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=143779
1. Qué es REST
Un conjunto de principios de arquitectura para el desarrollo de aplicaciones y servicios web.
Surge en oposición a SOAP y WSDL, que son más complicados.
Grandes proveedores 2.0 usan APIs RESTful (Google, Facebook...)
El énfasis se pone en los recursos (¿objetos?) y su transferencia por HTTP.
2. Principios básicos
2.1. Uso explícito de métodos HTTP
Se deben utilizar como se definieron en la especificación original de HTTP 1.1 (RFC 2616). Esto significa:
- GET para consultas
- POST para altas
- PUT para actualizaciones
- DELETE para ...¿lo adivinas?
Usar un GET para insertar o actualizar un registro en una BD es posible pero no es acorde con los principios RESTful.
2.2. Protocolo cliente/servidor sin estado
Cada petición HTTP debe contener toda la información necesaria.
Esto debe facilitar la escalabilidad, no se debe hacer que un servidor recupere el contexto si, por ejemplo, tiene que redirigir la petición a otro servidor para balanceo de carga.
Se transfiere el problema de mantener el estado al cliente.
Aunque a la hora de la práctica, aplicaciones que se definen como RESTful suelen usar cookies y otras formas de mantener el estado. ??
2.3. Exponer URIs como directorios y ficheros
Las URIs son así más intuitivas. Por ejemplo: http://www.dominio.com/informes/2016/04 nos podría devolver los informes de abril de 2016. Es una especie de sintaxis universal.
También se oculta la tecnología (no se ven extensiones de archivos, como ocurre en casos como http://www.dominio.com/informes/recuperar_informes.php donde se ve que se utiliza PHP). Así se puede cambiar la tecnología subyacente sin cambiar la URI.
Parece que este es el principio más REST de los cuatro.
4. Los recursos se devuelven en XML, HTML o JSON
Por ejemplo, un registro de una BD. Estos recursos pueden contener enlaces (URIs) a otros recursos.
3. Ideas importantes
Al exponer los recursos mediante una API RESTful, se facilita la integración de aplicaciones de diferente naturaleza y tecnologías. Se puede proporcionar una base de servicios REST que pueden utilizar otras aplicaciones más complejas para construir sobre ellos.
No todas las implementaciones públicas que se definen como REST son RESTful. Parece que el principio predominante que sí se suele respetar es el de la URI uniforme (principio 3), como podemos ver, por ejemplo, aquí.
También he detectado un poco de bombo en torno al término REST, pero esto es inevitable en esta profesión, donde los nuevos términos se utilizan a menudo como el equivalente tecnológico de cojonudo.
4. Más info
- RESTful Web services: The basics
- Versión en español del artículo anterior
- Artículo en la Wikipedia en español (mejor leerlo después de leer el primer enlace)
- Servicios que son "accidentalmente" RESTful
He leído también detalles en otras páginas, pero son cosas más puntuales, y nada que no podáis superar con una simple búsqueda.
jueves, 14 de abril de 2016
Espacio en disco en formato legible para las personas (linux)
$ df
y produce una salida tal que así
Por pequeño que sea un disco, siempre salen números muy grandes, ya que da el tamaño en bloques de 1K. Resulta un poco difícil de decir, a bote pronto, cuántos gigas libres te quedan.
Pero claro, normalmente nos gusta más leer el tamaño en megas y gigas. Para esto, viene muy bien la opción -h (legible por Humanos) del comando df.
$ df -h
Y ahora sí que se puede leer bien.
Como curiosidad, he marcado la tercerra línea para comparar cómo se ve esa misma información con el explorador Nautilus de Ubuntu, mostrando las propiedades del equipo.
Y se ve algo así
Una curiosidad es que Nautilus y el comando df no muestran exactamente el mismo espacio usado (10,6 GB en un caso y 10G en el otro). Curioso.
Por cierto, me estoy quedando sin espacio en la partición /boot. Habrá que hacer algo.
miércoles, 9 de marzo de 2016
Servidores web más populares en el IBEX 35
Entre otras cosas, vimos el valor de la propiedad "Server", que nos informaba del servidor web y algunas tecnologías que podían estar disponibles en el servidor (PHP, ASP...).
Pues bien, hace unos días me surgió la pregunta de qué servidores web son los más populares entre las "empresas grandes". Veamos cómo podemos usar cURL y los filtros de Linux (lo mismo se puede hacer en Windows) para hacer una pequeña estadística de esto.
Primero, como "empresas grandes" he considerado las que forman parte del IBEX 35.
Si queremos ver lo que nos devuelven un par de ellas, podemos escribir algo así:
curl -I --url www.abertis.com --url www.endesa.com
Y obtendríamos esta respuesta
Podemos filtrar sólo aquellas cabeceras que tengan la cadena "Server:"
curl -I --url www.abertis.com --url www.endesa.com |grep "Server:"
Como se puede ver, al usar cURL en pipelines nos va mostrando una estadística con los porcentajes de descargas, un medidor de progreso (cURL lo llama "Progress Meter"). De momento lo ignoraremos.
En el ejemplo anterior he puesto únicamente dos URLs, pero como me interesan las 35 URLs de las empresas del IBEX 35, lo que haré será poner estas URLs en un fichero y usar la opción -K de cURL para decirle que tome de allí las URLs.
Ya podemos invocar a cURL con ese fichero:
curl -I -K urls_ibex35.txt |grep "Server:"
Con la opción -s (silent) podemos hacer que desaparezca el medidor de progreso.
curl -I -K urls_ibex35.txt -s |grep "Server:"
Vale, ya estamos cerca del final. Podemos ver unas cuantas cosas interesantes. En primer lugar, no todos los servidores devuelven la cabecera "Server". De hecho, hay tan sólo 26 resultados, lo cual quiere decir que más de la cuarta parte de los administradores web de estas empresas (9 servidores) han decidido ocultar la información acerca del servidor web que utilizan. Además, uno de ellos devuelve una cadena en blanco, lo cual es lo mismo que ocultarlo. Total, 10 de 35 no lo indican, casi un 29%. Lo cual quiere decir que el otro 71% ven con buenos ojos publicar esta información, aunque con diferentes niveles de detalle. Y todo esto, asumiendo que no hayan falseado esta cadena para devolver un valor erróneo, cosa que también cae dentro de lo posible (aunque lo considero poco probable, la verdad).
Por último, en vez de contar a mano, hagamos un poco de "pipelining".
curl -I -K urls_ibex35.txt -s |grep "Server:" |sort |uniq -c |sort -nr
Como vemos, el ganador es...(redoble de tambores...)... ¡APACHE!
Apache está disponible en 12 de los 26 sitios web que publican la información, o sea, un 46%. Una gran diferencia con respecto a la segunda posición, ocupada por diferentes versiones del servidor de Microsoft, IIS, con 4 sitios (un 15%) y en tercera posición hay un (sorprendente para mí, pues no lo conocía) servidor llamado AkamaiGHost, que tienen 3 de las 26 empresas (casi un 12%).
Bueno, me parecen unos resultados interesantes de conocer. Además, no puede uno evitar pensar enseguida en extender esta estadística a otras empresas, universidades, organismos oficiales, gobiernos... Pero eso lo dejaremos para otra ocasión.
¿Y tu organización, qué servidor web utiliza? ¿Publica su nombre, versión, tecnologías asociadas...?
lunes, 22 de febrero de 2016
Convertir a mayúsculas o minúsculas con el comando dd
Para ello,hay que teclear algo así
dd if=a.txt of=b.txt conv=ucase
Donde if significa "input file" y of significa "output file".
Si cambiamos "ucase" por "lcase" transformaremos todo el texto en minúsculas.
Atentos a los caracteres con tildes.
lunes, 8 de febrero de 2016
Configurar la cabecera Server en el servidor web Apache 2 (Ubuntu y Windows)
Fig. 1. Valores posibles para la directiva ServerTokens
Vale. Entonces ¿dónde está el fichero de configuración?
EN UBUNTU
Ubicación del fichero de configuración: /etc/apache2/apache2.conf
Como se ve, ahora se está devolviendo el valor Apache a secas.
EN WINDOWS
Lo único que cambia en Windows es el fichero de configuración, que en este caso es apache\conf\httpd.conf o, como ocurre en mi caso, puedo cambiarlo en el fichero extra\httpd-default.conf, que es incluido en el primero mediante una directiva
# Various default settings
Include "conf/extra/httpd-default.conf"
Pues nada, ya sabemos cómo configurar la cabecera Server. Cuidado con lo que enviáis a vuestros clientes.
viernes, 15 de enero de 2016
Cabeceras en comunicaciones HTTP con cURL
Con curl podemos ver las cabeceras que nos está enviando un servidor web usando la opción -I. Por ejemplo, para ver las cabeceras que envía el servidor de la RAE:
Como se puede ver en la Fig. 1, una de las cabeceras se llama Server. En el caso de la RAE, el valor es Apache, lo cual nos puede llevar a pensar que la RAE utiliza como servidor web Apache.
Digo "nos puede llevar a pensar" porque esto no es necesariamente así. Al fin y al cabo, el administrador del servidor web puede configurarlo para que devuelva en esa cabecera cualquier cadena de texto que quiera.
Algunos administradores, por motivos de seguridad, prefieren no informar a los visitantes del servidor web que utilizan, por ponérselo un paso más difícil a los hackers (esta técnica de ocultación tampoco contribuye demasiado, la verdad, pero algo es algo). Otros dejan tan sólo el nombre del servidor web y eliminan los números de versión (informar del nombre y versión del servidor web suele ser la opción configurada por omisión). Así, al menos, intentan dificultar que un hacker pueda explotar alguna vulnerabilidad conocida que sea específica de esa versión del servidor web o del motor de PHP, de ASP...
En este caso, podemos comprobar que el administrador del servidor web de la RAE ha eliminado la versión de la cadena que describe el servidor Apache (suponiendo que sea ese el servidor real, lo cual asumiremos por el momento).
Otras veces, en las cabeceras podemos encontrar si el servidor está utilizando PHP, ASP u otras tecnologías, incluyendo el número de versión:
Pero recordad, dado que lo que se devuelve es una cadena de texto libremente modificable, también puede darse el caso, siendo un poco más retorcido, de que se esté informando de un número de versión erróneo, a fin de despistar al visitante. Así que no conviene, como decía Microsoft en un anuncio, asumir que "por llamarse Mercedes, eso signifique que sea un coche".
martes, 15 de diciembre de 2015
Manejo del portapapeles desde la línea de comandos
Dos comandos especialmente populares en el mundo Mac permiten esto: pbcopy y pbpaste. Las iniciales "pb" significan "pasteboard", que es como llaman al portapapeles en los Mac, mientras que en Linux y Windows suele usarse más el término "clipboard", aunque en el fondo esto es cuestión de caprichos.
Aunque pbcopy y pbpaste sean los más famosos, los otros sistemas operativos también tienen comandos que podemos utilizar para lograr lo mismo. En Windows, tenemos el comando clip.exe (aunque me parece que sólo funciona para copiar, pero el pegado ha de ser interactivo).
En Linux tenemos bastantes más opciones, mi elección ha sido el comando xsel. No viene en la distribución estándar, pero se instala fácilmente con
$ sudo apt-get install xsel Si investigais un poco encontraréis otras herramientas (xclip...), con multitud de opciones.
De todas formas, tan populares se han hecho los nombres originales del mundo Mac que yo he optado por seguir el consejo que dan en muchas páginas web: hacer un alias para esos comandos. Para ello, en el fichero de configuración $HOME/.basrc no hay más que añadir estas dos líneas:
alias pbcopy='xsel --clipboard --input'
alias pbpaste='xsel --clipboard --output'
Aquí podéis ver un ejemplo para copiar un calendario al portapeles desde la línea de comandos y pegarlo luego tanto en la línea de comandos como en un editor de texto:
Para sacarle partido a fondo, ya se sabe:
$ man xsel | lessAle, a copiar y pegar y copiar y pegar y copiar y pegar.






























