Lo primero, es decidir el método de diálogo con el servidor: GET o POST (puede haber alguno más, pero estos son los tradicionales).
Cuando pones algo como esto:
<FORM id="F0" name="F0" method="POST" action="cgi-bin/pruebautf8.pl?A1">estás indicando que vas a usar el método POST (los parámetros se transmitirán por la entrada estándar), pero en el URL estás poniendo "?A1", como si quisieras usar el método GET.
Hacer esto no es lo normal. Se puede, desde luego, pero el programa, en el servidor, debe acordarse de leer tanto la entrada estándar como los parámetros pasados en la URL.
Y el envío de parámetros es distinto (distinta codificación) según se envíen por GET o por POST. Supongamos que tenemos este formulario:
Using html4strict Syntax Highlighting
<form id="F0" name="F0" method="GET" action="cgi/pruebautf8.pl">
<label for="cadena">Escribir caracteres para guardar en la base</label>
<input type="hidden" name="t" value="A1">
<input type="text" name="cadena" id="cadena" value="">
<br><br>
<input type="submit" value="Guardar sin Ajax">
</form>
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
Observa que he quitado el "?A1" del URL. Con una petición GET, el navegador web lo quitará y pondrá en su lugar los campos del formulario. Por eso he creado un campo oculto, de un parámetro llamado "t", con el valor 'A1'.
Cuando el usuario pulsa el botón de envío, se compone la siguiente petición:
http://joaquin.com.es/cgi/pruebautf8.pl?t=A1&cadena=%C3%B1o%C3%B1%C3%A9Vemos que los caracteres ("ñoñé") van codificados con dos bytes, pues van en la codificación indicada por la página, utf8.
Si cambiamos GET por POST, pues ya sabemos que los parámetros irán en un paquete HTTP especial, y entrarán por la entrada estándar a nuestro programa.
Entonces... en nuestro programa, debemos saber por dónde nos llegan los parámetros, y aplicar distintas estrategias para decodificarlos a partir de ese utf8.
Pongamos un escenario de ejemplo, basado en el tuyo. No es el mismo código HTML (había algunos errores HTML) ni código Perl (el tuyo es demasiado prolijo para lo que hoy en día se usa). Pero hacen básicamente lo mismo.
Using html4strict Syntax Highlighting
<!DOCTYPE HTML>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Prueba UTF8</title>
<script type="text/javascript" language="javascript" src="/javascript/jquery/jquery.js"></script>
<script type="text/javascript" language="javascript">
$(document).ready(function() {
$("#conajax").click(function(e) {
e.preventDefault();
$.ajax({
url: "cgi/pruebautf8_get.pl",
cache: false,
async: false,
type: "GET",
dataType: "html",
data: {
t:'A2',
cadena:$("#cadena").val()
},
success: function(data) {
alert(data);
}
});
});
});
</script>
</head>
<body>
<h1>Pruebas UTF8 para guardar en la base con ajax y sin ajax.</h1>
<h2>Caracteres especiales: [áéíóúüñçÁÉÍÓÚÜÑÇ]</h2>
<h3>Método GET</h3>
<form id="F0" name="F0" method="GET" action="cgi/pruebautf8_get.pl">
<label for="cadena">Escribir caracteres para guardar en la base</label>
<input type="hidden" name="t" value="A1">
<input type="text" name="cadena" id="cadena" value="">
<br><br>
<input type="submit" value="Guardar sin Ajax">
</form>
<br><br>
<a href="#" id="conajax">Guardar con Ajax</a>
</body>
</html>
Coloreado en 0.002 segundos, usando
GeSHi 1.0.8.4
Y este es el programa en la parte del servidor:
Using perl Syntax Highlighting
#!/usr/bin/perl
# Joaquín Ferrero. 2013.
# Prueba de Ajax con caracteres utf8
#
use Encode; # de/codificación
use CGI qw(:standard ); # procesamiento de los parámetros
use CGI::Carp qw(fatalsToBrowser); # en caso de error grave, verlos en la ventana del navegador web
use strict; # activar modo estricto
use warnings; # activar las advertencias
use utf8; # lo pongo porque más abajo hay algunos caracteres tildados
use open OUT => ':utf8'; # indico que las salidas de archivos serán en utf8,
use open ':std'; # especialmente STDIN, STDOUT y STDERR
my $parametro = param('t'); # no decodificamos el parámetro "t" porque sabemos
# que solo va a valer dos valores: A1 y A2,
# y esos valores no tienen tildes :)
if ( $parametro eq 'A1' ) { AlmacenaCaracteres('Sin') } # caso sin Ajax
elsif ( $parametro eq 'A2' ) { AlmacenaCaracteres('Con') } # caso con Ajax
else { # algo pasó: pintar página de error
print
header(-charset => 'utf-8'), # vamos a sacar una cabecera HTTP. Indicamos además que lo mandaremos en utf8
start_html('Error'), # empezamos a enviar el código HTML de la página de error. El título será 'Error'
h1('Error'), # dentro de la página, un gigantesco 'Error' :)
hr, # una regla horizontal, para hacer bonito
p('Caracteres especiales: [áéíóúüñçÁÉÍÓÚÜÑÇ]'), # una demostración de que la salida de caracteres tildados funciona bien
hr,
;
my @parametros = param(); # volcado de todos los parámetros recibidos, en caso de haber alguno
foreach my $par ( @parametros ) { # recorremos todos los parámetros
print p($par); # primero ponemos en un párrafo, el nombre del párrafo
print blockquote(param( $par )); # seguido por el valor del parámetro
}
print
end_html, # terminamos la página de error
;
}
# fin de programa
# subrutinas
sub AlmacenaCaracteres {
my $sin_con = shift; # con gas o sin gas
my $cadena = decode('utf8', param('cadena')); # decodificación del parámetro 'cadena'
print # esta es la respuesta que mandamos fuera
header( # creamos una cabecera HTTP
-charset => 'utf-8', # lo que vamos a enviar va a estar en utf8
-type => 'text/plain', # vamos a enviar puro texto
),
"$sin_con Ajax: $cadena", # y este es el texto que enviamos
;
}
Coloreado en 0.002 segundos, usando
GeSHi 1.0.8.4
Lo que hacemos, principalmente, es decodificar los parámetros que nos llegan, porque sabemos que están en utf8. Los pasamos por decode() para obtener caracteres Unicode. Y finalmente, mandamos el resultado hacia el usuario. Como la salida estándar sabemos que la hemos declarado que estará en utf8 (la línea del
use open), entonces todos los print() hacen la conversión hacia el utf8. Y como en la cabecera de respuesta siempre ponemos un charset utf-8, pues entonces el navegador sabe cómo mostrarlos correctamente.
Ahora pasemos al método POST. El HTML es el mismo, solo que cambiamos las tres apariciones 'GET' por 'POST':
Using html4strict Syntax Highlighting
<!DOCTYPE HTML>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Prueba UTF8</title>
<script type="text/javascript" language="javascript" src="/javascript/jquery/jquery.js"></script>
<script type="text/javascript" language="javascript">
$(document).ready(function() {
$("#conajax").click(function(e) {
e.preventDefault();
$.ajax({
url: "cgi/pruebautf8.pl",
cache: false,
async: false,
type: "POST",
dataType: "html",
data: {
t:'A2',
cadena:$("#cadena").val()
},
success: function(data) {
alert(data);
}
});
});
});
</script>
</head>
<body>
<h1>Pruebas UTF8 para guardar en la base con Ajax y sin Ajax.</h1>
<h2>Caracteres especiales: [áéíóúüñçÁÉÍÓÚÜÑÇ]</h2>
<h3>Método POST</h3>
<form id="F0" name="F0" method="POST" action="cgi/pruebautf8_post.pl">
<label for="cadena">Escribir caracteres para guardar en la base</label>
<input type="hidden" name="t" value="A1">
<input type="text" name="cadena" id="cadena" value="">
<br><br>
<input type="submit" value="Guardar sin Ajax">
</form>
<br><br>
<a href="#" id="conajax">Guardar con Ajax</a>
</body>
</html>
Coloreado en 0.002 segundos, usando
GeSHi 1.0.8.4
El programa será casi el mismo, pero ahora haremos que la respuesta con Ajax sea solo texto (va a ser impreso solo en un alert()), mientras que la respuesta sin Ajax, será una página HTML completa:
Using perl Syntax Highlighting
#!/usr/bin/perl
# Joaquín Ferrero. 2013.
# Prueba de Ajax con caracteres utf8
#
use Encode;
use CGI qw(:standard );
use CGI::Carp qw(fatalsToBrowser);
use strict;
use warnings;
use utf8;
use open OUT => ':utf8';
use open ':std';
my $parametro = param('t');
if ( $parametro eq 'A1' ) { AlmacenaCaracteresSinAjax() }
elsif ( $parametro eq 'A2' ) { AlmacenaCaracteresConAjax() }
else {
print
header(-charset => 'utf-8'),
start_html('Error'),
h1('Error'),
hr,
p('Caracteres especiales: [áéíóúüñçÁÉÍÓÚÜÑÇ]'),
hr,
;
my @parametros = param();
foreach my $par ( @parametros ) {
print p($par);
print blockquote(param( $par ));
}
print
end_html,
;
}
#
sub AlmacenaCaracteresSinAjax {
my $cadena = decode('utf8', param('cadena')); # Decodificación
print # vamos a devolver una página HTML entera
header(
-charset => 'utf-8',
-type => 'text/html',
),
start_html,
"Sin Ajax: $cadena",
end_html,
;
}
sub AlmacenaCaracteresConAjax {
my $cadena = decode('utf8', param('cadena')); # Decodificación
print # en este caso sacamos solo texto
header(
-charset => 'utf-8',
-type => 'text/plain',
),
"Con Ajax: $cadena",
;
}
Coloreado en 0.002 segundos, usando
GeSHi 1.0.8.4
Hacemos exactamente el mismo procesamiento que hacíamos antes, con los parámetros. El módulo CGI se encarga de recogerlos desde la salida estándar, sabiendo si llegan por GET o POST, y los va dejando para que los obtengamos con param(). Solo nos queda hacer el decode(), y ya está. La pequeña diferencia con el programa anterior es que en el modo sin Ajax, se devuelve un código HTML completo. Esto es lo que genera, en ese caso:
Using html4strict Syntax Highlighting
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Untitled Document</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
Sin Ajax: ñoñé
</body>
</html>
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
Y el resultado sale correcto, igual que antes con GET.
Aquí surge una duda: ¿por qué no activamos, con 'use open', el decodificado de la entrada estándar? Si vamos a recibir la información por la entrada estándar -método POST-, sería lógico pensar que podemos ahorrarnos las llamadas decode().
Bueno, pues resulta que no debemos hacerlo: los datos enviados sí que llegan por la entrada estándar, pero no llegan como una ristra de bytes codificados en utf8, sino codificados igual a como los recibimos vía GET (codificados en URL). Además, estaremos marcando toda la entrada como que ya está codificada en utf8 -cuando en realidad no ha hecho nada-, y Perl pondrá una bandera especial para indicarlo. Si intentamos sacar los datos de forma directa, saldrán mal (no fueron decodificados). Aún más estropicio: si los pasamos por decode() sí que los veremos bien, pero no si lo pasamos por decode_utf8(). Según el manual de Encode, estas dos líneas deberían hacer lo mismo:
my $cadena = decode('utf8', param('cadena'));
my $cadena = decode_utf8(param('cadena'));
pero no es así (al menos hasta la v2.44 de Encode; la v2.55 ya está corregida). Resulta que decode_utf8() comprueba antes de hacer la decodificación si los datos ya están en utf8, y si es así, no hace nada.
Como resulta que los datos sí están marcados como utf8 (por efecto del 'use open'), entonces no realiza ninguna decodificación.
Así que lo mejor, en este caso, es no decodificar la entrada estándar de forma automática, y usar el decode() del Encode, para esa tarea.
Finalmente, queda el caso más peculiar, que es el que propones: usar el método POST, pero con un URL en el que aparece un parámetro. Aquí tenemos que analizar tanto lo que nos llega por POST como la propia URL. Por fortuna, CGI nos ayuda bastante:
Using html4strict Syntax Highlighting
<!DOCTYPE HTML>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Prueba UTF8</title>
<script type="text/javascript" language="javascript" src="/javascript/jquery/jquery.js"></script>
<script type="text/javascript" language="javascript">
$(document).ready(function() {
$("#conajax").click(function(e) {
e.preventDefault();
$.ajax({
url: "cgi/pruebautf8.pl?t=A2",
cache: false,
async: false,
type: "POST",
dataType: "html",
data: {
cadena:$("#cadena").val()
},
success: function(data) {
alert(data);
}
});
});
});
</script>
</head>
<body>
<h1>Pruebas UTF8 para guardar en la base con Ajax y sin Ajax.</h1>
<h2>Caracteres especiales: [áéíóúüñçÁÉÍÓÚÜÑÇ]</h2>
<h3>Método POST</h3>
<form id="F0" name="F0" method="POST" action="cgi/pruebautf8_post_url.pl?t=A1">
<label for="cadena">Escribir caracteres para guardar en la base</label>
<input type="text" name="cadena" id="cadena" value="">
<br><br>
<input type="submit" value="Guardar sin Ajax">
</form>
<br><br>
<a href="#" id="conajax">Guardar con Ajax</a>
</body>
</html>
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
He movido los parámetros 't' de donde estaban y los he colocado en el URL, como lo tenías tú. Todo lo demás, es igual al método POST anterior.
El programa es exactamente igual, salvo que, por no aburrir, haremos una pequeña variación en la hoja HTML devuelta en Sin Ajax, para que muestre más información.
Using perl Syntax Highlighting
#!/usr/bin/perl
# Joaquín Ferrero. 2013.
# Prueba de Ajax con caracteres utf8
#
use Encode;
use CGI qw(:standard );
use CGI::Carp qw(fatalsToBrowser);
use strict;
use utf8;
use open qw(:utf8 :std);
my $parametro = url_param('t'); # sabemos que el parámetro 't' viene por la URL
if ( $parametro eq 'A1' ) { AlmacenaCaracteresSinAjax() }
elsif ( $parametro eq 'A2' ) { AlmacenaCaracteresConAjax() }
else {
print
header(-charset => 'utf-8'),
start_html('Error'),
h1('Error'),
hr,
p('Caracteres especiales: [áéíóúüñçÁÉÍÓÚÜÑÇ]'),
hr,
end_html,
;
}
#
sub AlmacenaCaracteresSinAjax {
my $cadena = decode('utf8', param('cadena')); # Decodificación
print
header(
-charset => 'utf-8',
-type => 'text/html',
),
start_html,
p("$^V $Encode::VERSION"), # Versiones de Perl y Encode
p("Sin Ajax: $cadena. " . length($cadena) . " " . (utf8::is_utf8($cadena) ? 'si':'no') . ' es utf8'),
pre(unpack 'H*', $cadena), # vemos la ristra de octetos que tenemos en $cadena
end_html,
;
}
sub AlmacenaCaracteresConAjax {
my $cadena = decode('utf8', param('cadena')); # Decodificación
print
header(
-charset => 'utf-8',
-type => 'text/plain',
),
"Con Ajax: $cadena",
;
}
Coloreado en 0.002 segundos, usando
GeSHi 1.0.8.4
Como sabemos que recibimos un parámetro por la URL, pues tenemos que usar url_param() para acceder a él.
Esto es lo que sale en el access.log del servidor web:
xx.xx.xxx.xxx - - [19/Sep/2013:02:55:07 +0200] "POST /cgi/pruebautf8_post_url.pl?t=A1 HTTP/1.1" 200 532 "http://joaquin.com.es/pruebautf8_post_url.html"A mi no me gusta nada esta mezcla de parámetros, unos en la URL, y otros por POST... pero bueno, funciona.
Y ya está. El truco está en lo siguiente:
- el 'use open' determina que lo que recibimos y enviamos será en codificación utf8, así que nos simplifica la conversión de lo que enviamos fuera
- el decode() de Encode nos sirve para decodificar lo que nos llega por los parámetros, ya sea por GET (en el URL) o por POST (vía entrada estándar)
- el charset a 'utf-8' (atento al guión '-'), que ponemos en todas las cabeceras HTTP de respuesta, para que el navegador sepa qué va a recibir
Por complementar: se puede simplificar el programa aún más.
Si el programa que estamos haciendo no va a recibir archivos binarios, sino solo parámetros escalares normales (texto), podemos decirle a CGI que realice la conversión a utf8 por nosotros. Supongamos que usamos la versión POST del HTML anterior. El programa queda así:
Using perl Syntax Highlighting
#!/usr/bin/perl
# Joaquín Ferrero. 2013.
# Prueba de Ajax con caracteres utf8
#
use CGI qw(:standard :utf8); # aquí es donde pedimos a CGI la decodificación utf8 automática de los parámetros
use CGI::Carp qw(fatalsToBrowser);
use strict;
#use utf8; # no es necesario, porque en este programa no hay caracteres tildados
use open OUT => ':utf8'; # Salida en utf8
use open ':std'; # incluida la salida estándar
my $parametro = param('t');
if ( $parametro eq 'A1' ) { AlmacenaCaracteresSinAjax() }
elsif ( $parametro eq 'A2' ) { AlmacenaCaracteresConAjax() }
#
sub AlmacenaCaracteresSinAjax {
my $cadena = param('cadena'); # no es necesaria ninguna decodificación
print
header(
-charset => 'utf-8',
-type => 'text/html',
),
start_html,
p("Sin Ajax: $cadena. "),
end_html,
;
}
sub AlmacenaCaracteresConAjax {
my $cadena = param('cadena'); # no es necesaria ninguna decodificación
print
header(
-charset => 'utf-8',
-type => 'text/plain',
),
"Con Ajax: $cadena",
;
}
Coloreado en 0.001 segundos, usando
GeSHi 1.0.8.4
Los dos 'use open' equivalen también a un simple
binmode(STDOUT, ':utf8'), pero el primer 'use open' alcanza a todos los archivos abierto en escritura, que tengamos en el programa.
Perdón por el rollo, pero creo que así queda más claro.
P.D.: Fíjate en la cantidad de líneas que módulos como CGI nos ahorran: se encarga tanto de leer los parámetros como de generar el HTML. No vale ni es el indicado para todos los casos, pero es perfecto para ejemplos pequeños.