El Análisis de URLs

Introducción

Un Uniform Resource Identifier (URI) es una cadena de caracteres que identifica o da nombre a un recurso. Una URI puede ser un Uniform Resource Locator (URL) o un Uniform Resource Name (URN) o ambos. Un URN es una URI que identifica el recurso dentro del contexto de un espacio de nombres particular. Por ejemplo la URN urn:isbn:0-395-36341-1 identifica un libro pero - a diferencia de lo que ocurre en una URL - no nos dice como localizarlo.

El ejemplo de descarga de páginas HTML introducido en la sección 3.9 pone de manifiesto un problema: El intento de analizar los componentes de un Uniform Resource Locator (URL) utilizando expresiones regulares está condenado al fracaso. Los URLs tiene una estructura definida en RFC 2396 y RFC 2732.

Estos son los componentes de una URL típica y sus nombres:

   http://user:pass@example.com:992/animal/bird?species=seagull#wings
   \___/  \________/\_________/\__/\__________/\______________/\____/
     |        |          |       |       |             |          |
  protocol  login       hosts    port    path         query      anchor/fragment

Análisis de una URI con el Módulo URI

Para analizar una URL se debe usar el módulo URI. Veamos un ejemplo de uso:

pp2@nereida:~/src/perl/LWP$ cat -n uri.pl
     1  #!/usr/local/bin/perl -w
     2  use strict;
     3  use URI;
     4
     5  my $surl = shift || die "Se esperaba una URL\n";
     6  my $url = URI->new($surl);
     7
     8  print "Scheme:   '",$url->scheme(),"'\n";
     9  print "Userinfo: '",$url->userinfo(),"'\n";
    10  print "Host:     '",$url->host(),"'\n";
    11  print "Port:     '",$url->port(),"'\n";
    12  print "Path:     '",$url->path(),"'\n";
    13  print "Query:    '",$url->query(),"'\n";
    14  print "Fragment: '",$url->fragment(),"'\n";
Siguen las salidas de dos ejecuciones del programa anterior:
pp2@nereida:~/src/perl/LWP$ uri.pl http://user:pass@example.com:992/animal/bird?species=seagull#wings
Scheme:   'http'
Userinfo: 'user:pass'
Host:     'example.com'
Port:     '992'
Path:     '/animal/bird'
Query:    'species=seagull'
Fragment: 'wings'
pp2@nereida:~/src/perl/LWP$ uri.pl http://example.com/animal/bird#wings
Scheme:   'http'
Use of uninitialized value in print at ./uri.pl line 9.
Userinfo: ''
Host:     'example.com'
Port:     '80'
Path:     '/animal/bird'
Use of uninitialized value in print at ./uri.pl line 13.
Query:    ''
Fragment: 'wings'

Contrucción de una URI a partir de una Cadena

Los únicos caracteres permitidos en el path de una URL son los que casan con m{[\w:_.!~*',:@&+$()/]} mientras que en el query son los que pertenecen al lenguaje m{[\w_.!~*'()-}. Cualquier otro carácter debe ser codificado URL (también conocido como Percent-encoding y URL encoding): expresado mediante un símbolo de tanto por ciento seguido de los dígitos hexadecimales para ese carácter.

La función uri_escape en el módulo URI::Escape realiza el codificado URL de cualesquiera caracteres que deban ser codificados:

pp2@nereida:~/src/perl/LWP$ perl -MURI::Escape -wde 0
main::(-e:1):   0
  DB<1> $nombre = uri_escape("Juan Hernández")
  DB<2> $query = "name=$nombre+age=35+country=Spain"
  DB<3> print $query
name=Juan%20Hern%E1ndez+age=35+country=Spain

La función uri_escape_utf8 codifica los caracteres en UTF-8 antes de escaparlos.

El Método Clone

El método clone nos permite copiar un objeto URI:

pp2@nereida:~/src/perl/LWP$ perl -wd uri.pl http://user:pass@example.com:992/animal/bird?species=seagull#wings
main::(uri.pl:5):       my $surl = shift || die "Se esperaba una URL\n";
  DB<1> c 8
main::(uri.pl:8):       print "Scheme:   '",$url->scheme(),"'\n";
  DB<2> $copy = $url->clone()
  DB<3> x ($url, $copy)
0  URI::http=SCALAR(0x83fd460)
   -> 'http://user:pass@example.com:992/animal/bird?species=seagull#wings'
1  URI::http=SCALAR(0x83fdc88)
   -> 'http://user:pass@example.com:992/animal/bird?species=seagull#wings'

Los Setters de los Objetos URI

Se pueden usar los métodos anteriores como setters:
  DB<4> $copy->path('/weblogs')
  DB<5> x ($copy->path, $url->path)
0  '/weblogs'
1  '/animal/bird'

URIs en Contexto de Cadenas

Al tratar un objeto de la clase URI como una cadena se obtiene la cadena que describe la URL:

  DB<6> print "$copy"
http://user:pass@example.com:992/weblogs?species=seagull#wings

Normalización de URIs

Se denomina normalización de una URL o canonización de una URL al resultado de transformar la URL de una manera consistente de manera que sea posible determinar si dos cadenas URLs son equivalentes.

canonical normaliza una URI:

pp2@nereida:~/src/perl/LWP$ perl -MURI -wde 0
  DB<1> $u = URI->new('HTTP://user:pass@example.com:992/animal/bird?species=%41eagull%2ewings')
  DB<2> $w = $u->canonical()
  DB<3> x ($u, $w)
0  URI::http=SCALAR(0x83ffc84)
   -> 'HTTP://user:pass@example.com:992/animal/bird?species=%41eagull%2ewings'
1  URI::http=SCALAR(0x84004ac)
   -> 'http://user:pass@example.com:992/animal/bird?species=Aeagull.wings'

Comparación de URIs

El método eq dice si las dos URIs son iguales. dos URIs son iguales si su representación canónica es la misma:
  DB<4> p $u->eq($w)
1
Si lo que se quiere ver es que las dos referencias son iguales se debe usar el operador ==
  DB<5> p $u == $w

  DB<6> $z = $u

  DB<7> p $u == $z
1
El operador eq devuelve TRUE si las dos cadenas asociadas son iguales:
  DB<8> p $u eq $z
1
  DB<9> p $u eq $w

Recuerde que las componentes que describen el fichero y directorio diferencian entre el uso de mayúsculas y minúsculas (si es que el S.O. lo hace) Esto no ocurre con la componente que define el dominio.

Métodos Auxiliares

Métodos auxiliares de utilidad son path_query, path_segments, host_port y default_port:

pp2@nereida:~/src/perl/LWP$ perl -MURI -wde 0
main::(-e:1):   0
  DB<1> $u = URI->new('http://user:pass@example.com:992/animal/bird?species=seagull#wings')
  DB<2> x $u->path_query
0  '/animal/bird?species=seagull'
  DB<3> x $u->path_segments                      # contexto de lista
0  ''
1  'animal'
2  'bird'
  DB<4> x scalar($u->path_segments)              # contexto escalar
0  '/animal/bird'
  DB<5> x $u->host_port
0  'example.com:992'
  DB<6> x $u->default_port
0  80



Ausencia de Ciertas Componentes

En general, cuando una componente no existe el correspondiente método devuelve undef:

  DB<7> $v = URI->new('http://example.com/index.html')
  DB<8> print "not defined" unless defined($v->query)
not defined

Sin embargo ciertas componentes no tiene sentido en ciertas clases de URLs. Por ejemplo un URL del tipo mailto: no dispone de una componente host. En tal caso, la llamada al método host produce una excepción:

  DB<9> $m = URI->new('mailto:name@domain.es')
  DB<10> print $m->host
Can't locate object method "host" via package "URI::mailto" \
   at (eval 18)[/usr/share/perl/5.8/perl5db.pl:628] line 2.
La excepción puede evitarse comprobando el esquema o usando el método UNIVERSAL can:
 DB<11> $x = $m->can('host')? $m->host : "No host"
 DB<12> print $x
No host

Argumentos de una LLamada

En los primeros tiempos de internet los argumentos de una llamada a un procesador de un formulario (queries) eran cadenas separadas por el signo '+'

pp2@nereida:~/src/perl/LWP$ perl -MURI -wde 0
  DB<1> $v = URI->new('http://www.example.com/search.pl?LWP+module+perl')
  DB<2> x $v->query_keywords()
0  'LWP'
1  'module'
2  'perl'
El método query_keywords permite obtener la lista de argumentos cuando la llamada ocurre de acuerdo con este protocolo. Posteriormente se ha adoptado una forma de llamada con nombres:

  DB<4> $w = URI->new('http://www.example.com/search.pl?lib=LWP&word=module&language=perl')
  DB<5> x $w->query_form()
0  'lib'
1  'LWP'
2  'word'
3  'module'
4  'language'
5  'perl'
en este caso usamos el método query_form

Conversión de una URL Relativa en una Absoluta

Una URL absoluta comienza con el esquema y contiene todas las componentes que el esquema requiere. Por ejemplo, la URL

http://nereida.deioc.ull.es/~pp2/perlexamples/node37.html
es absoluta.

Cualquier URL que no comienza con un esquema es relativa. Para determinar el recurso al que se refiere una URL relativa es necesario prefijar una URL base absoluta. Se usa una notación que semeja la notación Unix para describir la jerarquía de archivos:

Las siguientes URL son relativas:

El método new_abs permite la conversión de URLs relativas en absolutas:
        $abs = URI->new_abs($relative, $base);
Por ejemplo:
casiano@beowulf:/tmp/Net-Server-0.96/examples$ perl -MURI -dwe 0
  DB<1> $base = 'http://nereida.deioc.ull.es/~pp2/perlexamples/node37.html'
  DB<2> $can = URI->new_abs('../apuntes/node127.html', $base)->canonical
  DB<3> x $can
0  URI::http=SCALAR(0x83e75c0)
   -> 'http://nereida.deioc.ull.es/~pp2/apuntes/node127.html'
  DB<4> p $can
http://nereida.deioc.ull.es/~pp2/apuntes/node127.html

Conversión de una URL Absoluta en una Relativa

El método rel permite la conversión de URLs absolutas en relativas:

        $rel = $absolute->rel($absolute_base);
Por ejemplo:
casiano@beowulf:/tmp/Net-Server-0.96/examples$ perl -MURI -dwe 0
DB<1> $pp2_37 = URI->new('http://nereida.deioc.ull.es/~pp2/perlexamples/node37.html')
DB<2> $pl_37 = URI->new('http://nereida.deioc.ull.es/~pl/perlexamples/node37.html')
DB<3> p $pl_37->rel($pp2_37)
../../~pl/perlexamples/node37.html



Subsecciones
Casiano Rodríguez León
2010-03-22