Páginas

sábado, 23 de septiembre de 2017

Resumen SCRUM

Hola

Dejo el enlace de un pequeño resumen sobre SCRUM que recopilé hace unos meses. El contenido es:

Historia de Scrum
Marco técnico de Scrum
Roles
   Scrum Team
   Product Owner
   Scrum Master
   Development Team
Artefactos
   Temas y epics
   Historias de usuario
   Product Backlog
   Sprint Backlog
   Incremento
   Gráfico de producto (Burn up)
   Gráfico de avance (Burn down)
   Scrum Task Board
   Estimación Planning poker
Eventos
   Sprint
   Reunión de planificación del sprint
   Scrum diario
   Revisión del sprint
   Retrospectiva del sprint
   Refinamiento

El enlace al documento es:

   https://mega.nz/#!tcpBSIjI!oG_p7E_pzvthMs3niWL3cObvhd7eTdET2Y7SKAHzbNs

Un saludo

Problemas en Ubuntu al copiar ficheros desde disco duro a discos externos NTFS vía USB

Hola

En las últimas versiones de Ubuntu 16.04, 16.10 y 17.04 siempre he tenido problemas para copiar ficheros grandes (desde varios cientos de MB en adelante) desde el disco duro a unidades NTFS externas vía USB.

Al copiar estos ficheros la velocidad de copiado se va degradando poco a poco, llegando a bajar a velocidades de unos pocos cientos de bytes/sec. Si dejas el proceso de copia lanzado horas y horas
finalmente la copia se efectúa correctamente y el sistema se recupera, pero mientras se ejecuta el proceso de copiado el rendimiento general del sistema se degrada, llegando incluso a dejar de responder el ratón y el teclado, con lo que he llegado a tirar del cable USB o incluso a reiniciar el PC, cosa que no he tenido que hacer en Linux nunca.

Después de mucho googlear he dado con una solución PARCIAL que me permite una velocidad de copiado normal, PERO QUE SOLO FUNCIONA MEDIANTE LINEA DE COMANDOS lanzando cosas como:
 
   cp /path-origen/mi-fichero-grande /media/egdepedro/usbdisk

EL TEMA NO FUNCIONA SI COPIAMOS DESDE GNOME USANDO POR EJEMPLO NAUTILUS, POR ESE LADO HAY QUE SEGUIR INVESTIGANDO......

El problema viene por la extrema lentitud de los accesos USB, pero hay claramente algún bug que lleva ya años apareciendo y que no he notado en versiones anteriores de Ubuntu.

La solución es ajustar ciertos parámetros del kernel vía '/proc/sys/vm' y hacer luego esos ajustes permanentes en todos los arranques. Los pasos son:

1) Creamos el fichero de configuración del servicio /etc/systemd/system/rc.local.service con el siguiente comando:

   sudo gedit /etc/systemd/system/rc.local.service

2) Copiamos en el fichero de configuración del servicio el siguiente contenido:

[Unit]
Description=/etc/rc.local Compatibility
ConditionPathExists=/etc/rc.local

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
StandardOutput=tty
RemainAfterExit=yes
SysVStartPriority=99

[Install]
WantedBy=multi-user.target

3) Creamos el script /etc/rc.local con el siguiente comando:

   sudo gedit /etc/rc.local

4) Copiamos en el script el siguiente contenido:

#!/bin/sh
echo 'tupasswordroot' | sudo -S su
echo $((16*1024*1024)) > /proc/sys/vm/dirty_background_bytes
echo $((48*1024*1024)) > /proc/sys/vm/dirty_bytes

5) Cambiamos los permisos al script para que sea ejecutable

   sudo chmod 755 /etc/rc.local

6) Por último activamos el servicio y lo iniciamos con los comandos:

   sudo systemctl enable rc.local.service
   sudo systemctl start rc.local.service

7) Podemos verificar que el servicio está bien generado con el comando:

   sudo systemctl status rc.local.service

   Que debería devolver algo de tipo:

systemctl status rc.local.service
   rc.local.service - /etc/rc.local Compatibility
   Loaded: loaded (/etc/systemd/system/rc.local.service; enabled; vendor preset: enabled)
   Active: active (exited) since Sat 2017-09-23 01:15:22 CEST; 1min 2s ago

14) Reiniciamos el PC y ya podemos copiar a la unidad USB ficheros grandes de forma ágil y sin afectar al rendimiento del sistema. OJO COPIANDO SIEMPRE DESDE LINEA DE COMANDOS.

Eso sería todo.

Nota. Para restaurar los valores de los parámetros del kernel 'dirty_background_bytes' y 'dirty_bytes' deberíamos hacer lo siguiente:

   sudo systemctl stop rc.local.service
   sudo systemctl disable rc.local.service
   sudo rm /etc/systemd/system/rc.local.service
   sudo rm  /etc/rc.local
   cd /proc/sys/vm
   sudo su
   echo 0 > /proc/sys/vm/dirty_background_bytes
   echo 0 > /proc/sys/vm/dirty_bytes
 
   Y tras reiniciar el PC todo volvería a estar como al comienzo.

Referencias:

https://lwn.net/Articles/572911/
https://unix.stackexchange.com/questions/107703/why-is-my-pc-freezing-while-im-copying-a-file-to-a-pendrive
https://www.netroby.com/view/3895

Usando ffmpeg para recortar un trozo de video

Para recortar N segundos, por ejemplo 20 segundos a partir del minuto 10, de una pelicula con ffmpeg necesitamos debemos lanzar el comando siguiente:

ffmpeg -i pelicula.avi -vcodec copy -acodec copy -ss 00:10:00.000 -t 00:10:20.0000 trozo.avi

Aclaraciones:

  -ss 00:10:00.000     --> Especifica que el corte comience en el instante 10:00.000
  -t 00:10:20.000       --> Especifica que el corte termine en el instante 10:20.000

Un saludo

martes, 19 de septiembre de 2017

¿Cómo cerrar una sesión de telnet en bash?

Hola

Cuando haces telnet contra cierta máquina, por ejemplo un server de smtp, con un comando como:

   telnet smtp.ejemplo.org 25

Obtenemos (si tenemos autorización para llegar a la máquina por ese puerto) una respuesta como:

   Trying xxx.xxx.xxx.Xxx...
   Connected to smtp.ejemplo.org.
   Escape character is '^]'.
   220 smtp.ejemplo.org ESMTP

La cuestión es, ¿cual es la combinación de teclas necesaria '^]' para cerrar la conexión?

Pues bien, la combinación de teclas es Ctrl + Alt_Gr + ]

Un saludo

jueves, 11 de mayo de 2017

Experimentos con gráficos en Javascript

Hola

En esta entrada voy a subir un pequeño juego de rebotes montado en JavaScript puro. El experimento se centra en el manejo de las opciones gráficas de un canvas y en el manejo de los intervalos de repetición en Javascript

El código HTML5 sería:

<!-- Definimos el documento como HTML 5 -->
<!DOCTYPE html>
<!-- Definimos el idioma de la página -->
<html lang="es">
<head>
<!-- Definimos el juego de caracteres de la página -->
<meta charset="UTF-8">
<!-- Definimos el autor de la página -->
<meta name="author" content="Eduardo García">
<!-- Describimos el contenido de la página -->
<meta name="description" content="Rebotes">
<!-- Describimos algunos términos clave de la página -->
<meta name="keywords" content="HTML5,CSS,JavaScript">
<!-- Un pequeño fichero de estilos -->
<link rel="stylesheet" type="text/css" href="./css/ejercicio33.css">
</head>
<body>
<!-- Aquí el contenido de la página -->
<h1>Experimentos con gráficos</h1>
<canvas id="canvas" width="640" height="480"></canvas>
<form>
<input type="button" id="inic" value="Iniciar partida" onclick="iniciar();"/>
<input type="button" id="fin" value="Finalizar partida" onclick="detener();"/>
</form>
<!-- Definimos nuestras dependencias de scripts externos -->
<script type="text/javascript" src="./js/rebotes.js" defer></script>
</body>
</html>

La hoja de estilos sería:

canvas { 
background: #eee; 
}

El código Javascript sería:

/**
 * Constantes
 */
var radioBola = 10;       //Radio de la bola
var paletaAltura = 2;     //Altura de la paleta de juego
var paletaAnchura = 75;   //Anchura de la paleta de juego
var velocBarra = 3;       //Velocidad de movimiento de la paleta
var velocX = 1;           //Velocidad inicial de movimiento de la bola en el eje X
var velocY = -1;   //Velocidad inicial de movimiento de la bola en el eje Y
var deltaVel = 1.2;       //Porcentaje de aumento de velocidad de la bola en cada raquetazo
var deltaAnchura = 1.1;   //Porcentaje de aumento de la anchura de la barra en cada raquetazo

/**
 * Variables globales
 */
var canvas;               //Area de dibujo
var ctx;                  //Objeto para dibujar en el canvas
var bolaX;                //Coordenada X de la bola
var bolaY;                //Coordenada Y de la bola (ojo Y crece hacia abajo de la pantalla)
var paletaX;              //Coordenada del borde izquierdo de la paleta
var flechaDerecha;        //Flag que indica si la flecha derecha se está pulsando
var flechaIzquierda;      //Flag que indica si la flecha izquierda se está pulsando
var ctrlIntervRepet;      //Variable para controlar la repeticion

/**
 * Lanzador del juego
 */
function iniciar() {

//Definimos el área de dibujo en 2D
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");

//Inicialmente centramos la bola
bolaX = canvas.width/2;
bolaY = canvas.height/2;

//Inicialmente centramos la barra 
paletaX = (canvas.width-paletaAnchura)/2;

//Inicialmente marcamos que no hay ninguna tecla pulsada
flechaDerecha = false;
flechaIzquierda = false;

//Fijamos el intervalo de repintado de la mesa en 10ms
ctrlIntervRepet = setInterval(dibujar, 10);

//Definimos listener para las acciones pulsar tecla y soltar tecla
document.addEventListener("keydown", pulsaTecla, false);
document.addEventListener("keyup", sueltaTecla, false);

//Reajustamos los botones
document.getElementById("inic").disabled = true;
document.getElementById("fin").disabled = false;
}

/**
 * Detiene la partida
 */
function detener(){
//Paramos la repeticion
clearInterval(ctrlIntervRepet);
//Reajustamos los botones
document.getElementById("inic").disabled = false;
document.getElementById("fin").disabled = true;
//Restauramos anchura de la paleta
paletaAnchura = 75;
//Restauramos la velocidad de la bola
velocX = 1;
velocY = -1;
}

/**
 * Controlamos qué tecla se pulsa
 */
function pulsaTecla(e) {

//Si se pulsa la flecha derecha (39) o la flecha izquierda (37)
    if(e.keyCode == 39) {
    flechaDerecha = true;
    } else if(e.keyCode == 37) {
    flechaIzquierda = true;
    }
}

/**
 * Controlamos qué tecla se suelta
 */
function sueltaTecla(e) {

//Si se pulsa la flecha derecha (39) o la flecha izquierda (37)
    if(e.keyCode == 39) {
    flechaDerecha = false;
    }
    else if(e.keyCode == 37) {
    flechaIzquierda = false;
    }
}

/**
 * Dibuja el talero, la bola y la barra
 */
function dibujar() {

    //Limpiamos el restángulo
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    //Dibujamos la bola
    ctx.beginPath();
    ctx.arc(bolaX, bolaY, radioBola, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
    
    //Dibujamos la barra
    ctx.beginPath();
    ctx.rect(paletaX, canvas.height-paletaAltura, paletaAnchura, paletaAltura);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
    
    //Hay rebote arriba
    if(bolaY + velocY < radioBola) {
    velocY = -velocY;
    }
    
    //Hay rebote abajo
    if(bolaY + velocY > canvas.height-radioBola) {
    //Vemos si hay barra bajo la bola
    if(bolaX<paletaX || bolaX>paletaX+paletaAnchura) {
    //Paramos la partida
    detener();
   
    //Preguntamos si quiere jugar de nuevo
    if(confirm("¿Quieres jugar de nuevo?")){
    //Iniciamos la partida
    iniciar();
    }
    } else {
    //Tenemos rebote en la barra
    velocY = -velocY;
   
    //Incrementamos la velocidad un deltaVel
    velocX = velocX*deltaVel;
    velocY = velocY*deltaVel;
   
    //Hacemos crecer la barra en un deltaAnchura
    paletaAnchura = paletaAnchura*deltaAnchura;
    }
    }

    //Hay rebote izquierda o derecho
    if(bolaX + velocX > canvas.width-radioBola || bolaX + velocX < radioBola) {
    velocX = -velocX;
    }
    
    //Movemos la bola
    bolaX += velocX;
    bolaY += velocY;
    
    //Movemos la barra sin salirnos del borde
    if(flechaDerecha) {
    if (paletaX+paletaAnchura+velocBarra<canvas.width){
    paletaX += velocBarra;
    }
    } else if(flechaIzquierda) {
    if (paletaX-velocBarra>0){
    paletaX -= velocBarra;
    }
    }
}

Un saludo

Peticiones AJAX (GET, POST, PUT y DELETE) usando jQuery

Hola

En esta entrada vamos a ver cómo invocar métodos GET, POST, PUT y DELETE mediante jQuery.

Utilizaremos el API REST ofertado en la URL https://reqres.in/api

Montaremos una página HTML como la siguiente:

<!-- Definimos el documento como HTML 5 -->
<!DOCTYPE html>
<!-- Definimos el idioma de la página -->
<html lang="es">
<head>
<!-- Definimos el juego de caracteres de la página -->
<meta charset="UTF-8">
<!-- Definimos el autor de la página -->
<meta name="author" content="Eduardo García">
<!-- Describimos el titulo de la página -->
<title>AJAX y jQuery</title>
<!-- Describimos algunos términos clave de la página -->
<meta name="keywords" content="HTML5,CSS,JavaScript,jQuery">
</head>
<body>
<!-- Aquí el contenido de la página -->
<h1>AJAX&amp;JQUERY I. Ejemplos de llamadas GET, POST, PUT y DELETE a un servicio REST.</h1>
<!-- Aquí meteremos los usuarios -->
<div id="usuarios"></div>
<!-- Definimos nuestras dependencia con jQuery -->
<script type="text/javascript" src="js/jquery-3.2.1.js"></script>
<!-- Definimos nuestros propios ficheros Javascript -->
<script type="text/javascript" src="js/ejemplo.js"></script>
</body>
</html>

El contenido de nuestro fichero javascript con jQuery 'ejemplo.js' sería:

/**
 * En este ejemplo usaremos un servicio REST gratuíto que está
 * disponible en Internet y que ofrece métodos GET, POST, PUT 
 * y DELETE para operar con 'usuarios'
 */ 
var urlListaUsuarios = "https://reqres.in/api/users?page=1";
var urlGetUsuario = "https://reqres.in/api/users/";
var urlAltaUsuario = "https://reqres.in/api/users";
var urlBajaUsuario = "https://reqres.in/api/users/";
var urlModificaUsuario = "https://reqres.in/api/users/";

$(document).ready(getListaUsuarios);
//$(document).ready(getUsuario(2));
//$(document).ready(altaUsuario);
//$(document).ready(bajaUsuario(2));
//$(document).ready(modificaUsuario(2));

/**
 * Obtenemos por GET la primera página de una lista de usuarios
 */
function getListaUsuarios(){

$.getJSON(urlListaUsuarios)
.done(function(respuesta, textStatus, jqXHR ) {
$("#usuarios").append("<h3>Recuperados los siguientes usuarios:</h3>");
$.each(respuesta.data, function(i,result){
$("#usuarios").append("<div>Id: "+ result.id + 
 " Nombre: " + result.first_name + 
 " Apellido: " + result.last_name + 
 " Foto: <img src="+result.avatar+"></img></div>");
});
})
.fail(function( jqXHR, textStatus, errorThrown ) {
$("#usuarios").append("Error llamando al servicio: " + textStatus);
});
}

/**
 * Obtenemos por GET los datos del usuario con el id recibido
 */
function getUsuario(idUsuario){

$.getJSON(urlGetUsuario+idUsuario)
.done(function(result, textStatus, jqXHR ) {
$("#usuarios").append("<h3>Recuperado el siguiente usuario:</h3>");
$("#usuarios").append("<div>Id: "+ result.data.id + 
 " Nombre: " + result.data.first_name + 
 " Apellido: " + result.data.last_name + 
 " Foto: <img src="+result.data.avatar+"></img></div>");
})
.fail(function( jqXHR, textStatus, errorThrown ) {
$("#usuarios").append("Error llamando al servicio: " + textStatus);
});

}

/**
 * Damos de alta a un nuevo usuario
 */
function altaUsuario(){

$.ajax({
type: "POST",
dataType: "json",
url: urlAltaUsuario,
data: {"name": "eduardo", "job": "developer"}
})
.done(function(result, textStatus, jqXHR ) {
$("#usuarios").append("<h3>Creado el siguiente usuario:</h3>");
$("#usuarios").append("<div>Id: "+ result.id + 
 " Nombre: " + result.name + 
 " Puesto: " + result.job + 
 " Fecha alta: " + result.createdAt );
})
.fail(function( jqXHR, textStatus, errorThrown ) {
$("#usuarios").append("Error llamando al servicio: " + textStatus);
});

}

/**
 * Damos de baja al usuario con el id recibido
 */
function bajaUsuario(idUsuario){

$.ajax({
type: "DELETE",
dataType: "json",
url: urlBajaUsuario+idUsuario
})
.done(function(result, textStatus, jqXHR ) {
$("#usuarios").append("<h3>Borrado correctamente el usuario: "+idUsuario+"</h3>");
})
.fail(function( jqXHR, textStatus, errorThrown ) {
$("#usuarios").append("Error llamando al servicio: " + textStatus);
});

}

/**
 * Modificamos los datos del usuario recibido por parametro
 */
function modificaUsuario(idUsuario){

$.ajax({
type: "PUT",
dataType: "json",
url: urlModificaUsuario+idUsuario,
data: {"name": "EduardoX", "job": "DeveloperX"}
})
.done(function(result, textStatus, jqXHR ) {
$("#usuarios").append("<h3>Modifiacdo correctamente el usuario: "+idUsuario+"</h3>");
$("#usuarios").append("<div>Nombre: " + result.name + 
 " Puesto: " + result.job + 
 " Fecha modificacion: " + result.updatedAt );
})
.fail(function( jqXHR, textStatus, errorThrown ) {
$("#usuarios").append("Error llamando al servicio: " + textStatus);
});

}

viernes, 17 de febrero de 2017

Descargar vídeos de Youtube con Ubuntu desde la terminal

Hola

Tras probar sin éxito varias extensiones de Firefox y Chrome que permitiesen descargar vídeos desde YouTube he googleado un poco encontrando una herramienta de línea de comando muy buena y fácil de manejar llamada 'youtube-dl'.

Para instalarla teclearemos:

$ sudo apt-get install youtube-dl

Su utilización es muy simple, basta con movernos a la carpeta donde deseemos que se almacene el vídeo, por ejemplo el escritorio:

$ cd $HOME/Escritorio

Luego lanzamos la herramienta mediante el comando:

$ youtube-dl URL_VIDEO_YOUTUBE

Por ejemplo:

$ youtube-dl https://www.youtube.com/watch?v=PlLHc60egiQ

Con lo que se descargará el vídeo deseado

Un saludo

Eliminar Malware Extensions de Google Chrome en Ubuntu

Hola

Tras instalar una cierta extensión (video-download-helper) de Google Chrome, que se anunciaba como extensión para la descarga de videos de Youtube, me he dado cuenta de que dicha extensión no solo no descargaba vídeos sino que además levantaba ventanas de publicidad y cerraba Chrome cada vez que intentaba acceder a la configuración de extensiones para desinstalarla.

En fin, no se cómo Google ofrece este tipo de extensiones en su área de descargas (https://chrome.google.com/webstore/detail/video-download-helper/mngdadkapbemiekajhhalpakdpleogfn?hl=es), muchos usuarios pueden llegar a desinstalar el navegador definitivamente, porque las extensiones no desaparecen al reinstalar Chrome.

Para desinstalar la extensión a mano debemos acceder a la carpeta:

$HOME/.config/google-chrome/Default/Extensions

Donde encontraremos varios directorios con nombres como:

mlomiejdfkolichcflejclcbmpeaniij
nmmhkkegccagdldgiimedpiccmgmieda
pkedcjkdefgpdelpbcmbmeomcjbeemfm

He optado por borrar todos los directorios, dado que no he tenido ganas de entretenerme en averiguar cual era el directorio asociado a la extensión malware en cuestión:

$ rm -R *

Tras lo cual he reiniciado Chrome y he podido acceder a la zona de configuración de extensiones con normalidad, limpiando los 'residuos' de las extensiones anteriormente existentes y reinstalando mis extensiones favoritas.

Un saludo


miércoles, 18 de enero de 2017

Miniservidor con Node.js y oracledb

Hola

En esta entrada vamos a montar un miniservidor html con Node.js que conecte a cierta tabla de Oracle mediante oracledb. Los pasos para instalar todo son:

1) Instalamos node.js y su gestor de paquetes npm

sudo apt-get install nodejs
sudo apt-get install npm

2) Verificamos que está bien instalado con:

nodejs --version                   --> v4.2.6
npm --version                      --> 3.5.2

3) Instalamos algunas de las librerías javascript que vamos a necesitar en nuestro servidor 'miserver.js':

   npm install http
   npm install httpdispatcher

4) Descargamos el 'Oracle Instant Client' de Oracle (http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html) siguiendo las instrucciones de (https://github.com/oracle/node-oracledb). Necesitaremos los packages 'basic' y 'SDK' si la BBDD está en remoto (es lo habitual). Por lo que hemos bajado las versiones de RedHat/linux:

   oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.rpm
   oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.rpm

5) Para Ubuntu no hay instalador, así que transformamos el instalador rpm (RedHat) con:

   sudo alien -i oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.rpm
   sudo alien -i oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.rpm

Nota Ubuntu. Tambien hemos necesitado instalar la libreria libaio, no se para qué hace falta

   sudo apt-get install libaio1

6) Seguidamente instalamos la librería javascript oracledb con:

   npm install oracledb

Nota Ubuntu. Para poder lanzar sin problemas la instalacion de oracledb (saltaba un error en 'node-gyp rebuild' descrito en (https://github.com/npm/npm/issues/11335)) he tenido que crear un softlink 'node' dado que se petaba por tener instalado nodejs (el package de ubuntu) en vez del paquete node 'oficial'. Para crear el softlink lanzamos:

   sudo ln -s /usr/bin/nodejs /usr/local/bin/node

8) Creamos nuestro servidor web por el puerto 8888 en javascrip, para ello creamos un fichero llamado 'miserver.js' con el siguiente codigo:


//Importamos las librerias necesarias
var http = require('http');
var HttpDispatcher = require('httpdispatcher');
var oracledb = require('oracledb');

//Inicializamos el enrutador
var enrutador = new HttpDispatcher();

//Definimos la función que manejara las peticiones
function handleRequest(request, response){
        console.log("Peticion:");
        console.log(request.url);
        enrutador.dispatch(request, response);
}

//Creamos el servidor y lo ponemos a escuchar en el puerto 8888
var server = http.createServer(handleRequest);
server.listen(8888, function(){
    console.log("Arrancado y escuchando en el puerto 8888");
});

//Creamos la función que devuelve la pagina /
enrutador.onGet("/", function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<?xml version='1.0' encoding='UTF-8'?>");
  res.write("<!DOCTYPE html>");
  res.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
  res.write("<head>");
  res.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
  res.write(" <meta name='language' content='es'/>");
  res.write("</head>");
  res.write("<body>");
  res.write("<h3> Primer miniportal con Node.js</h3>");
  res.write(" <ul>");
  res.write("   <li><a href='/listaprovincias'>Lista de provincias</a></li>");
  res.write(" </ul>");
  res.write("</body>");
  res.end();
});

//Creamos la función que devuelve la pagina /listaprovincias
enrutador.onGet("/listaprovincias", function(req, res) {
 
  oracledb.getConnection(
    {
      user          : "user",
      password      : "pwd",
      connectString : "127.0.0.1:1535/prueba"
    },
    function(err, connection) {
        if (err) {
           console.error(err);
           return;
        }
        connection.execute("SELECT CPRO, DPRO FROM PROVINCIAS ORDER BY DPRO",
        function(err, result) {
          if (err) {
             console.error(err.message);
             connection.close();
             return;
          }
          console.log("Encontradas "+result.rows.length+" provincias");

      res.writeHead(200, {'Content-Type': 'text/html'});
      res.write("<?xml version='1.0' encoding='UTF-8'?>");
      res.write("<!DOCTYPE html>");
      res.write("<html lang='es' xmlns='http://www.w3.org/1999/xhtml'>");
      res.write("<head>");
      res.write(" <meta http-equiv='Content-type' content='text/html; charset=UTF-8'/>");
      res.write(" <meta name='language' content='es'/>");
      res.write("</head>");
      res.write("<body>");
      res.write("<h3> Listado de provincias </h3>");
          res.write("<table>");
          res.write("<tr>");
          res.write("<th>CPRO</th>");
          res.write("<th>DPRO</th>");
          res.write("</tr>");
          for (reg in result.rows){
             res.write("<tr>");
             res.write("<th>"+result.rows[reg][0]+"</th>");
             res.write("<th>"+result.rows[reg][1]+"</th>");
             res.write("</tr>");
             console.log(result.rows[reg][0] + "---" + result.rows[reg][1]);
          }
          res.write("</table>");
      res.write("<a href='/'>Volver</a>");
      res.write("</body>");
          res.end();
        });
    });
});


9) Levantamos el servidor con:

node miserver.js

10) Desde el navegador lanzamos la siguiente URL: http://localhost:8888/

Eso es todo