Página 1 de 1

Módulos actuales en Perl

NotaPublicado: 2016-11-07 21:03 @919
por hugo11ab
¿Qué tal? Buen día a todos.

Aquí nuevamente pidiéndoles sus apreciados consejos. Me están solicitando el desarrollo de un sistema en ambiente web que esté desarrollado en Perl y que contenga seguridad.

Anteriormente he desarrollado aplicaciones en ambiente web y todo era a base de tablas y utilizaba los siguientes módulos:

use CGI qw/:standard :html3/;
use DBI;
use CGI::Carp('fatalsToBrowser');
use CGI param,header,p;
use CGI::Session;
use HTML::Table;
use Time::localtime;
use DateTime;
use Number::Format qw(:subs);
use MIME::Base64;
use warnings;

Actualmente, ¿qué módulos podría utilizar para desarrollar el sistema antes mencionado en ambiente web donde pueda separar código Perl del HTML, utilizar HTML5, sesiones, implementar seguridad al sistema...

De antemano, muchas gracias.

Re: Módulos actuales en Perl

NotaPublicado: 2016-11-08 08:08 @380
por explorer
Puedes seguir usando lo mismo, pero se aconseja que todo código HTML vaya aparte, con algún sistema de plantillas, como Template Toolkit o Mason. Yo algunas veces usaba HTML::Template, ya que necesitaba hacer algo sencillo.

Para aumentar la seguridad en este tipo de entornos, lo mejor y más rápido es activar el modo Taint de Perl. En la primera línea del programa, donde está puesto el shebang, agregas '-T'. Algo así:

#!/usr/bin/perl -T

y el programa funcionará con el modo de "entintado" activado: no te dejará acceder de forma sencilla a los datos que te entregue el usuario, por lo que estarás obligado a "extraer" o "filtrar" la información que esperas del usuario, por medio de alguna expresión regular, por ejemplo.

Tienes más información en tu ordenador con el comando perldoc perlsec (en inglés).

Desde hace unos años, la comunidad está jubilando todos los módulos CGI, y se aconseja que este tipo de programas se desarrollen bajo el estándar PGSI.

Tienes una explicación muy buena en el documento CGI::Alternatives.

Re: Módulos actuales en Perl

NotaPublicado: 2016-11-09 18:00 @791
por explorer
Añado otro punto de vista.

También ayuda bastante la utilización de un entorno de trabajo ("framework") web, como Catalyst, Dancer, Mojolicious o CGI::Application.

Catalyst es completo, pero algo feo.

Dancer es muy moderno, pero con muchas dependencias.

Mojolicious tiene un montón de utilerías modernas, pero es muy grande ya que no quiere dependencias (que es otra ventaja), pero a veces rompe con la versión anterior.

CGI::Application es el entorno de trabajo web clásico. Quizás se ha quedado algo anticuado... pero también tiene su propia web :)

Si has estado trabajando con CGI y CGI::Session, CGI::Application es la evolución natural, ya que les engloba, pero hace años que no se actualiza.

Titanium es otro entorno de trabajo web, basado en CGI::Application.

Yo, por mí, escogería Mojolicious.

Re: Módulos actuales en Perl

NotaPublicado: 2016-11-09 21:36 @942
por hugo11ab
Gracias, explorer. Empezaré a ver la información.

Re: Módulos actuales en Perl

NotaPublicado: 2016-11-10 08:32 @397
por explorer
Por aquí tenemos un tutorial de Catalyst.

Re: Módulos actuales en Perl

NotaPublicado: 2017-05-05 13:45 @615
por memrobles
Hola, buenas compañeros.

Si la comunidad está dejando de usar CGI, ¿cómo hacemos ahora para el login de usuarios web?

Actualmente uso algo como esto, solo que lo conecte a MySQL:
(de hecho, es tu código, explorer :D )

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2.         #
  3.         # Ejemplo de CGI::Session para login
  4.         #
  5.         use strict;
  6.         use warnings;
  7.         use diagnostics;
  8.          
  9.         use CGI::Session;
  10.         use CGI ':standard';
  11.         use CGI::Carp qw'fatalsToBrowser warningsToBrowser';
  12.          
  13.          
  14.         ## Creamos el objeto CGI
  15.         my $cgi = CGI->new();  
  16.          
  17.         ## Creamos el objeto CGI::Session
  18.         my $session = CGI::Session->new("driver:File", $cgi, {'Directory'=>'/tmp/'})
  19.             or die CGI::Session->errstr;
  20.          
  21.         ## Enviamos la cookie de sesión al usuario
  22.         print $session->header;
  23.          
  24.         # Aquí guardaremos el perfil del usuario
  25.         my $perfil;
  26.          
  27.         ## Inicialización
  28.         if ( not $session->param('~registrado') ) {     # si el usuario no está registrado
  29.          
  30.             # vemos si se está registrando en este momento
  31.             if (my $nombre = $cgi->param('login_nombre')) {
  32.                 my $passwd = $cgi->param('login_passwd');  
  33.          
  34.                 # Vemos si es un usuario conocido por nosotros
  35.                 # si lo es, obtenemos su perfil
  36.                 if ($perfil = usuario_registrado($nombre, $passwd)) {
  37.                    
  38.                     # Guardamos el perfil asociado a la sesión
  39.                     $session->param('perfil', $perfil);
  40.                     $session->param('~registrado', 1);
  41.                 }
  42.                 else {  # Es un desconocido, le pedimos que rellene el registro otra vez
  43.                     presenta_login('Usuario desconocido');
  44.                     exit;
  45.                 }
  46.             }
  47.             else {      # No está registrado ni se está registrando
  48.                presenta_login('Identifíquese');
  49.                exit;
  50.             }
  51.         }
  52.         else {          # es un usuario registrado (hay una sesión de él)
  53.             # recuperamos el perfil, desde la sesión
  54.             $perfil = $session->param('perfil');
  55.         }
  56.          
  57.          
  58.         # A partir de aquí, sabemos qué usuario es, y su perfil
  59.         # Podemos presentarle las opciones propias de un usuario registrado
  60.         my $nombre = $perfil->{nombre};         # otra forma de hacerlo sería recuperar esta información
  61.         my $email  = $perfil->{email};          # desde la base de datos, ya que sabemos qué usuario es
  62.          
  63.         if ($cgi->param('Desconectar')) {       # caso de que el usuario quiera desconectarse
  64.             $session->clear(['~registrado']);   # olvidamos que estaba registrado
  65.          
  66.             print       # despedida
  67.                 start_html('Desconexión'),
  68.                 h1('Desconexión'),
  69.                 hr(),
  70.                 p("Adios, $nombre"),
  71.                 end_html()
  72.                 ;
  73.         }
  74.         else {          # una página normal
  75.             print
  76.                 start_html('Bienvenido'),
  77.                 h1('Bienvenido'),
  78.                 hr(),
  79.                 p("Bienvenido $nombre ($email)"),
  80.                 hr(),
  81.                 p(a({-href=>'ficheros.html'},'Ver lista de ficheros')),
  82.                 p(a({-href=>$cgi->url() . '?Desconectar=1'},'Desconectar')),
  83.                 end_html()
  84.                 ;
  85.         }
  86.          
  87.         # Perfil de un determinado usuario
  88.         sub usuario_registrado {
  89.             my ($nombre, $passwd) = @_;
  90.          
  91.             # aquí consultaríamos una base de datos, por ejemplo
  92.             if ($nombre eq 'JF'  and  $passwd eq '2010') {
  93.                 # si es un usuario en nuestra base de datos, recuperamos su perfil
  94.                 return { nombre => $nombre, email => '[email protected]' };
  95.             }
  96.              
  97.             return;
  98.         }
  99.          
  100.         # Presenta el formulario de entrada
  101.         sub presenta_login {
  102.             my $titulo = shift;
  103.          
  104.             print
  105.                 start_html,
  106.                 h1('Registro de entrada: ' . $titulo),
  107.                 start_form,
  108.                 p('Nombre: '     . textfield('login_nombre')),
  109.                 p('Contraseña: ' . password_field('login_passwd','',8,8)),
  110.                 p(),
  111.                 submit('Entrar'),
  112.                 end_form,
  113.                 end_html()
  114.                 ;
  115.         }
Coloreado en 0.005 segundos, usando GeSHi 1.0.8.4

Re: Módulos actuales en Perl

NotaPublicado: 2017-05-05 14:05 @628
por memrobles
Bueno, investigando un poco encontré algo http://blogs.perl.org/users/joel_berger/2012/10/a-simple-mojoliciousdbi-example.html

Seguiré investigando, disculpad mi ignorancia :oops:

Re: Módulos actuales en Perl

NotaPublicado: 2017-05-05 15:41 @695
por explorer
Para Mojolicious hay un complemento: Mojolicious::Plugin::Authentication

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. use Mojolicious::Plugin::Authentication
  2.  
  3. $self->plugin('authentication' => {
  4.     'autoload_user' => 1,
  5.     'session_key' => 'wickedapp',
  6.     'load_user' => sub { ... },
  7.     'validate_user' => sub { ... },
  8.     'current_user_fn' => 'user', # compatibility with old code
  9. });
  10.  
  11. if ($self->authenticate('username', 'password', { optional => 'extra data stuff' })) {
  12.     ...
  13. }
  14.  
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Esta es la forma más cómodo. Otra opción es usar el método under() para indicar qué rutas se deben ejecutar bajo una misma comprobación (verificar que el usuario se ha autenticado).

Otra forma de hacerlo, también más incómoda.

Re: Módulos actuales en Perl

NotaPublicado: 2017-05-05 18:03 @794
por memrobles
Gracias, explorer.

Mira, encontré esto.

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2.  
  3. use Mojolicious::Lite;
  4. use Mojolicious::Plugin::Authentication;
  5. use Mojolicious::Plugin::Bcrypt;
  6. use Mojolicious::Plugin::Database;
  7. use DBI;
  8.  
  9. #
  10. # The auth database contains the user accounts
  11. #
  12. # Sample schema:
  13. # CREATE TABLE user (user_id integer primary key,
  14. #                    user_name varchar,
  15. #                    user_passwd varchar);
  16. #
  17. # The user_passwd fields contains a bcrypt hash
  18. #
  19.  
  20. plugin 'database' => {
  21.     dsn      => 'dbi:SQLite:dbname=auth',
  22.     username => q{},
  23.     password => q{},
  24.     options  => { RaiseError => 1 },
  25.     helper   => 'db',
  26.  
  27. };
  28.  
  29. #
  30. # Use strong encryption
  31. #
  32.  
  33. plugin 'bcrypt';
  34.  
  35. #
  36. # Database-based authentication example
  37. #
  38.  
  39. plugin 'authentication' => {
  40.  
  41.     load_user => sub {
  42.  
  43.         my ( $self, $uid ) = @_;
  44.  
  45.         my $sth = $self->db->prepare(' select * from user where user_id=? ');
  46.  
  47.         $sth->execute($uid);
  48.  
  49.         if ( my $res = $sth->fetchrow_hashref ) {
  50.  
  51.             return $res;
  52.  
  53.         }
  54.         else {
  55.  
  56.             return;
  57.         }
  58.  
  59.     },
  60.  
  61.     validate_user => sub {
  62.  
  63.         my ( $self, $username, $password ) = @_;
  64.  
  65.         my $sth
  66.             = $self->db->prepare(' select * from user where user_name = ? ');
  67.  
  68.         $sth->execute($username);
  69.  
  70.         return unless $sth;
  71.  
  72.         if ( my $res = $sth->fetchrow_hashref ) {
  73.  
  74.             my $salt = substr $password, 0, 2;
  75.  
  76.             if ( $self->bcrypt_validate( $password, $res->{user_passwd} ) ) {
  77.  
  78.                 $self->session(user => $username);
  79.  
  80.                 #
  81.                 # For data that should only be visible on the next request, like
  82.                 # a confirmation message after a 302 redirect, you can use the
  83.                 # flash.
  84.                 #
  85.                
  86.                 $self->flash(message => 'Thanks for logging in.');
  87.  
  88.                 return $res->{user_id};
  89.  
  90.             }
  91.             else {
  92.  
  93.                 return;
  94.  
  95.             }
  96.  
  97.         }
  98.         else {
  99.  
  100.             return;
  101.  
  102.         }
  103.     },
  104.  
  105. };
  106.  
  107. #
  108. # This page is visible only to authenticated users
  109. #
  110.  
  111. any '/welcome' => sub {
  112.  
  113.     my $self = shift;
  114.  
  115.     if ( not $self->user_exists ) {
  116.  
  117.         $self->flash( message => 'You must log in to view this page' );
  118.  
  119.         $self->redirect_to('/');
  120.  
  121.         return;
  122.  
  123.     }
  124.     else {
  125.  
  126.         $self->render( template => 'welcome' );
  127.  
  128.     }
  129.  
  130. };
  131.  
  132. #
  133. # Try to log in the user
  134. #
  135.  
  136. any '/login' => sub {
  137.  
  138.     my $self = shift;
  139.  
  140.     my $user = $self->param('name') || q{};
  141.  
  142.     my $pass = $self->param('pass') || q{};
  143.  
  144.     if ( $self->authenticate( $user, $pass ) ) {
  145.  
  146.         $self->redirect_to('/welcome');
  147.  
  148.     }
  149.     else {
  150.  
  151.         $self->flash( message => 'Invalid credentials!' );
  152.  
  153.         $self->redirect_to('/');
  154.  
  155.     }
  156.  
  157. };
  158.  
  159. #
  160. # Close the session
  161. #
  162.  
  163. any '/logout' => sub {
  164.  
  165.     my $self = shift;
  166.  
  167.     $self->session( expires => 1 );
  168.  
  169.     $self->redirect_to('/');
  170.  
  171. };
  172.  
  173. #
  174. # If logged in, show the welcome page
  175. # Show the login form otherwise
  176. #
  177.  
  178. any '/' => sub {
  179.  
  180.     my $self = shift;
  181.  
  182.     if ( $self->session('name') ) {
  183.  
  184.         return $self->redirect_to('/welcome');
  185.  
  186.     }
  187.     else {
  188.  
  189.         $self->render( template => 'login' );
  190.  
  191.     }
  192.  
  193. };
  194.  
  195. #
  196. # Change the default secret key for signing the cookies
  197. # This passphrase is used by the HMAC-MD5 algorithm to make signed cookies
  198. # secure and can be changed at any time to invalidate all existing sessions.
  199. #
  200.  
  201. app->secret('9dd1571a116fccce362d54996c3d8c70c101cad5');
  202.  
  203. #
  204. # Run!
  205. #
  206.  
  207. app->start;
  208.  
  209. __DATA__
  210. @@ layouts/default.html.ep
  211. <!doctype html><html>
  212. <head><title><%= title %></title></head>
  213. <body><%= content %></body>
  214. </html>
  215. @@ login.html.ep
  216. % layout 'default';
  217. % title 'Login';
  218. <h1>Log In</h1>
  219. <% if (my $message = flash 'message' ) { %>
  220.     <b><%= $message %></b><br>
  221. <% } %>
  222. <%= form_for login => (method => 'post') => begin %>
  223.     Name: <%= text_field 'name' %>
  224.     <br>
  225.     Password: <%= password_field 'pass' %>
  226.     <br>
  227.     <%= submit_button 'Login' %>
  228. <% end %>
  229. @@ index.html.ep
  230. % layout 'default';
  231. % title 'Welcome';
  232. <% if (my $message = flash 'message' ) { %>
  233.     <b><%= $message %></b><br>
  234. <% } %>
  235. @@ welcome.html.ep
  236. % title 'Welcome page';
  237. <% if (my $message = flash 'message' ) { %>
  238.     <b><%= $message %></b><br>
  239. <% } %>
  240. Welcome <%= session 'user' %>.<br>
  241. <%= link_to Logout => 'logout' %>
Coloreado en 0.004 segundos, usando GeSHi 1.0.8.4


Pero, para ser sincero, no entiendo nada. :(

Slds

Re: Módulos actuales en Perl

NotaPublicado: 2017-05-07 16:52 @745
por explorer
Te pongo el código, comentado. Y abajo te explico el funcionamiento.
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2.  
  3. use Mojolicious::Lite;                                  # Aplicación simple
  4. use Mojolicious::Plugin::Authentication;                # Complemento de autenticación
  5. use Mojolicious::Plugin::Bcrypt;                        # Complemento de codificación
  6. use Mojolicious::Plugin::Database;                      # Complemento de acceso a BD
  7. use DBI;                                                # Funciones de acceso a BD
  8.  
  9.  
  10. #
  11. # La base de datos de autenticación contiene las cuentas de usuario
  12. #
  13. # Ejemplo de esquema:
  14. # CREATE TABLE user (user_id integer primary key,
  15. #                    user_name varchar,
  16. #                    user_passwd varchar);
  17. #
  18. # La contraseña contiene un hash codificado con bcrypt
  19. #
  20.  
  21. # Carga complemento de acceso a la base de datos
  22.  
  23. plugin 'database' => {
  24.         dsn      => 'dbi:SQLite:dbname=auth',
  25.         username => q{},
  26.         password => q{},
  27.         options  => { RaiseError => 1 },
  28.         helper   => 'db',
  29. };
  30.  
  31.  
  32. # Usar codificación fuerte
  33.  
  34. plugin 'bcrypt';                                       
  35.  
  36.  
  37. # Ejemplo de autenticación basado en base de datos
  38.  
  39. plugin 'authentication' => {
  40.  
  41.         # Subrutina para acceder a los datos del usuario
  42.  
  43.         load_user => sub {
  44.                 my ( $self, $uid ) = @_;
  45.  
  46.                 # Creación de la consulta
  47.                 my $sth = $self->db->prepare(' select * from user where user_id=? ');
  48.                 $sth->execute($uid);
  49.  
  50.                 # Hacer la consulta
  51.                 if ( my $res = $sth->fetchrow_hashref ) {
  52.  
  53.                         # Si la respuesta es afirmativa, devolvemos los datos
  54.                         return $res;
  55.  
  56.                 }
  57.                 else {
  58.                         # Si ese usuario no existe o hay un error, salimos con error
  59.                         return;
  60.                 }
  61.  
  62.         },
  63.  
  64.  
  65.         # Subrutina para validar a un usuario
  66.  
  67.         validate_user => sub {
  68.                 my ( $self, $username, $password ) = @_;
  69.  
  70.                 # Creación de la consulta
  71.                 my $sth
  72.                         = $self->db->prepare(' select * from user where user_name = ? ');
  73.                 $sth->execute($username);
  74.  
  75.                 # Regresar con error si la consulta no funcionó
  76.                 return unless $sth;
  77.  
  78.                 if ( my $res = $sth->fetchrow_hashref ) {
  79.  
  80.                         # Esta línea no se usa (?)
  81.                         my $salt = substr $password, 0, 2;             
  82.  
  83.                         # Comparar el $password que nos pasa el usuario con el que obtenemos de la BD
  84.                         if ( $self->bcrypt_validate( $password, $res->{user_passwd} ) ) {
  85.  
  86.                                 # Si la contraseña es correcta, creamos una sesión
  87.                                 $self->session(user => $username);
  88.  
  89.                                 #
  90.                                 # Para los datos que deberían ser visibles solo para la siguiente petición,
  91.                                 # como un mensaje de confirmación después de un redirect 302, puedes usar
  92.                                 # flash.
  93.                                 #
  94.                                 $self->flash(message => 'Thanks for logging in.');
  95.  
  96.                                 return $res->{user_id};
  97.                         }
  98.                         else {
  99.                                 # Devolvemos un error si no coincide la contraseña
  100.                                 return;
  101.                         }
  102.                 }
  103.                 else {
  104.                         # Devolvemos un error si falló la consulta
  105.                         return;
  106.                 }
  107.         },
  108. };
  109.  
  110. #
  111. # Esta página solo es visible para usuarios autenticados
  112. #
  113.  
  114. any '/welcome' => sub {
  115.         my $self = shift;
  116.  
  117.         if ( not $self->user_exists ) {
  118.        
  119.                 # Si el usuario no existe, ponemos un mensaje de aviso, y lo enviamos a la página principal
  120.  
  121.                 $self->flash( message => 'You must log in to view this page' );
  122.                 $self->redirect_to('/');
  123.  
  124.                 return;
  125.         }
  126.         else {
  127.                 # Si el usuario existe, pintamos la pantalla de bienvenida
  128.                 $self->render( template => 'welcome' );
  129.         }
  130. };
  131.  
  132. #
  133. # Intentar registrar la entrada del usuario
  134. #
  135.  
  136. any '/login' => sub {
  137.         my $self = shift;
  138.  
  139.         # Credenciales que nos manda el usuario por el formulario
  140.         my $user = $self->param('name') || q{};
  141.         my $pass = $self->param('pass') || q{};
  142.  
  143.         # Si el usuario se autentica de forma correcta
  144.         if ( $self->authenticate( $user, $pass ) ) {
  145.  
  146.                 # le llevamos a la página de bienvenida
  147.                 $self->redirect_to('/welcome');
  148.         }
  149.         else {
  150.                 # si no, mensaje de error y regresamos a pantalla principal
  151.                 $self->flash( message => 'Invalid credentials!' );
  152.                 $self->redirect_to('/');
  153.         }
  154. };
  155.  
  156. #
  157. # Cerrar la sesión
  158. #
  159.  
  160. any '/logout' => sub {
  161.         my $self = shift;
  162.  
  163.         $self->session( expires => 1 );                 # Expirar la sesión
  164.         $self->redirect_to('/');                        # Ir a página principal
  165. };
  166.  
  167. #
  168. # Si está registrado, mostramos la página de bienvenida
  169. # Si no, mostramos el formulario de registro
  170. #
  171.  
  172. any '/' => sub {
  173.  
  174.         my $self = shift;
  175.  
  176.         # Si hay sesión
  177.         if ( $self->session('name') ) {
  178.  
  179.                 # Ir a página de bienvenida
  180.                 return $self->redirect_to('/welcome');
  181.         }
  182.         else {
  183.                 # Si no, mostrar formulario de registro
  184.                 $self->render( template => 'login' );
  185.         }
  186. };
  187.  
  188. #
  189. # Clave secreta que se usa para firmar las galletas (cookies)
  190. # Esta frase de paso lo usa el algoritmo HMAC-MD5 para crear galletas firmadas
  191. # seguras y se puede cambiar en cualquier momento para invalidar todas las sesiones actuales.
  192. #
  193.  
  194. app->secret('9dd1571a116fccce362d54996c3d8c70c101cad5');
  195.  
  196. #
  197. # ¡Ejecución!
  198. #
  199.  
  200. app->start;
  201.  
  202. __DATA__
  203. @@ layouts/default.html.ep
  204. <!doctype html><html>
  205. <head><title><%= title %></title></head>
  206. <body><%= content %></body>
  207. </html>
  208.  
  209. @@ login.html.ep
  210. % layout 'default';
  211. % title 'Login';
  212. <h1>Log In</h1>
  213. <% if (my $message = flash 'message' ) { %>
  214. <b><%= $message %></b><br>
  215. <% } %>
  216. <%= form_for login => (method => 'post') => begin %>
  217. Name: <%= text_field 'name' %>
  218. <br>
  219. Password: <%= password_field 'pass' %>
  220. <br>
  221. <%= submit_button 'Login' %>
  222. <% end %>
  223.  
  224. @@ index.html.ep
  225. % layout 'default';
  226. % title 'Welcome';
  227. <% if (my $message = flash 'message' ) { %>
  228. <b><%= $message %></b><br>
  229. <% } %>
  230.  
  231. @@ welcome.html.ep
  232. % title 'Welcome page';
  233. <% if (my $message = flash 'message' ) { %>
  234. <b><%= $message %></b><br>
  235. <% } %>
  236. Welcome <%= session 'user' %>.<br>
  237. <%= link_to Logout => 'logout' %>
Coloreado en 0.004 segundos, usando GeSHi 1.0.8.4


De la línea 23 a la 108 se define cómo trabajarán los complementos. El acceso a la base de datos (BD) y cómo validar y recuperar la información del usuario. La subrutina de las líneas 43 a la 62 sirve para recuperar información del usuario, que es devolver todos los campos de la fila que corresponde al nombre del usuario. Y la subrutina de las líneas 67 a la 107 comprueba las credenciales.

A partir de la 114 a la 186 se definen las "rutas" por las que el usuario puede navegar. Por ejemplo, la ruta de la 114 es cuando el usuario va a 127.0.0.1:3000/welcome

El funcionamiento va a así:

Se supone que el usuario empieza por la ruta principal "/" (que es lo mismo que decir que no ha puesto ninguna ruta), así que lo primero que se ejecuta es la ruta de la línea 172. Allí se comprueba si existe una sesión. Como no existe, presentamos la plantilla con el formulario de registro de entrada. Ese formulario está en la plantilla 'login' que está en la sección __DATA__ (la que se llama login.html.ep).

Cuando el usuario rellena el formulario y pulsa el botón, entramos en la ruta de la línea 136, donde comprobamos las credenciales. Esa comprobación (línea 144) es la que llama a la subrutina comentada antes de la línea 67, donde crea la sesión. Si el usuario es correcto, lo reenviamos a "/welcome".

Ahora entramos por la línea 114. Si un usuario no autenticado intenta entrar directamente, lo llevamos de vuelta a la principal "/". Si no, le presentamos la plantilla "/welcome" (welcome.html.ep) con un mensaje de bienvenida, y un enlace para salir de la sesión.

Si el usuario pulsa ese enlace, entramos por la ruta de la línea 160, donde borramos la sesión y regresamos a la principal.

Finalmente, la aplicación arranca como programa independiente en la línea 200. Ahí se crea una aplicación web a la que te puedes conectar con el navegador yendo a la dirección 127.0.0.1:3000