3. Servlets estándar

WEBrick viene con varios servlets que se pueden utilizar de inmediato.

3.1. FileHandler

FileHandler es uno de los servlets estándar más útiles. Si se especifica la opción :DocumentRoot, WEBrick instalará un FileHandler configurado para servir el directorio especificado. WEBrick automatiza por ti lo siguiente al fijar la opción :DocumentRoot.

El ejemplo de la sección anterior,


	start_webrick( :DocumentRoot => '/var/www' )
es funcionalmente equivalente a:

	start_webrick do |server|
	doc_root = '/var/www'
	server.mount( "/", HTTPServlet::FileHandler, doc_root,
		{:FancyIndexing=>true} )
	end
Este ejemplo define la opción :FancyIndexing del servlet FileHandler a "true". En la sección de configuración de este manejador se describen más opciones.

Si el camino de la petición referencia a un directorio, FileHandler servirá el fichero índice del mismo. Si no existe este fichero y se tiene la opción :FancyIndexing a "true" entonces se servirá un listado del directorio, si :FancyIndexing no es "true" se genera un código de error 403 (Prohibido).

3.1.1. Modificando los tipos MIME básicos

La clase FileHandler debe conocer el tipo MIME del fichero a servir para dar contendido adecuado a la cabecera Content-type del mensaje HTTP de respuesta. Para ello utiliza la extensión del fichero a servir asociando la extensión con el tipo MIME, ext => tipoMIME, por medio de una tabla. Esta tabla con los tipos básico, adecuada para la mayoría de los casos, se encuentra en Utils::DefaultMimeTypes

Si esta tabla no resulta adecuada o, quizá se desea utiliza el fichero de tipos MIME que utiliza la aplicación Apache instalada en el sistema (que normalmente se encuentra en /etc/mime.types), es posible configurar WEBrick para que la utilice.


	system_mime_table= Utils::load_mime_types( '/etc/mime.types' ) #Carga de los tipos MIME de Apache	
	my_mime_table= system_mime_table.update(
		{ "foo" => "application/foo" }	#Aquí se añade un tipo MIME 
	)

	start_webrick( :MimeTypes=> my_mime_table )

3.1.2. DefaultFileHandler

Cuando una clase FileHandler recibe una petición, analiza su path. Y delegará el tratamiento de la petición a un objeto de la clase DefaultFileHandler si este:

  • No termina en .cgi, ni

  • termina en .rhtml

Este DefaultFileHandler emite una cabecera ETag en base al contenido del inodo del fichero, tamaño y momento de la modificación. También utiliza otras cabeceras. A continuación se listan algunas cabeceras HTTP que utiliza junto con la explicación extraída de la RFC del protocolo HTTP/1.1.

if-modified-since

Esta cabecera de la petición se utiliza para hacer un método condicional: Si la variante solicitada no ha sido modificada desde la hora indicada en el campo, el servidor no devolverá ninguna entidad sino que la respuesta tendrá un código de estado 304 (no modificado) sin contendido en el cuerpo del mensaje.

if-none-match

Esta cabecera de la petición se utiliza también para hacer un método condicional. Un cliente con una o más entidades obtenidas previamente de un mismo origen puede verificar si ninguna de esas entidades ha sido actualizada incluyendo una lista de etiquetas de entidad en esta cabecera. Su propósito es lograr una actualización eficiente de la información cacheda con una cantidad mínima de sobrecarga por transacción. También se utiliza para evitar que el método PUT modifique inadvertidamente un recurso cuando el cliente cree que el recurso no existe.

if-range

Si el cliente tiene una copia parcial de una entidad en su cache, y desea tener una copia actualizada de toda ella, podrá utilizar la cabecera Range junto con un método GET condicional (utilizando una cabecera if-modified-since o if-match o ambas). Sin embargo, si la condición falla debido a que la entidad ha sido modificada, el cliente deberá hacer una segunda petición para obtener la entidad entera.

Este campo de cabecera permite al cliente "cortocircuitar" la segunda petición. Informalmente su significado es el siguiente, "si la entidad no ha sido modificada, envíame las parte o partes que he perdido; sino envíame de nuevo la entidad entera"

range

La presencia del campo de cabecera Range en un método GET incondicional modifica lo devuelto si el comando GET finaliza con éxito. En otras palabras, la respuesta lleva consigo un código de estado 206 (Contenido parcial) en vez del 200 (OK).

En un comando GET condicional (una petición que utiliza la cabecera if-modified-since o if-none-match o ambas, o if-unmodified-since o if-match o ambas) modifica lo devuelto si el GET termina con éxito y la condición es cierta. Si la condición es falsa, no afecta al código de estado 304 (No modificado) en la respuesta devuelta.

3.2. CGIHandler

¿Qué ocurre si se tienen algunos programas CGI que no se quieren o simplemente no se tiene el tiempo suficiente para reescribirlos como servlets WEBrick? No hay ningún problema, todavía se pueden utilizar. Basta con instalar un FileHandler sobre el directorio que contiene los programas CGI y asegurarse de que todos tiene el sufijo .cgi


	start_webrick {|server|
		cgi_dir = File.expand_path( '~jfglez/src/ruby/servlets/cgi-bin' )
		server.mount( "/cgi-bin", HTTPServlet::FileHandler, cgi_dir,
		{ :FancyIndexing=> true } )
	}

	jfglez:~$ cat ~jfglez/src/ruby/servlets/cgi_bin/test.cgi
	#!/usr/bin/env ruby
	print "Content-type: text/plain\r\n\r\n"
	ENV.keys.sort.each {|k| puts "#{k} ==> #{ENV[k]}"}

	[jfglez@localhost docs]$ w3m -dump http://localhost:8080/cgi-bin/test.cgi
	GATEWAY_INTERFACE==> CGI/1.1
	HTTP_ACCEPT==> text/*, image/*
	HTTP_ACCEPT_ENCODING==> gzip, compress, bzip, bzip2, deflate
	HTTP_ACCEPT_LANGUAGE==> en;q=1.0
	HTTP_HOST==> localhost:8080
	HTTP_USER_AGENT==> w3m/0.5
	PATH_INFO==>
	QUERY_STRING==>
	REMOTE_ADDR==> ::ffff:127.0.0.1
	REMOTE_HOST==> localhost
	REQUEST_METHOD==> GET
	REQUEST_URI==> http://localhost:8080/cgi-bin/test.cgi
	SCRIPT_FILENAME==> /home/jfglez/src/ruby/servlets/cgi-bin/test.cgi
	SCRIPT_NAME==> /cgi-bin/test.cgi
	SERVER_NAME==> localhost
	SERVER_PORT==> 8080
	SERVER_PROTOCOL==> HTTP/1.1
	SERVER_SOFTWARE==> WEBrick/1.3.1 (Ruby/1.8.1/2003-12-25)

Cuando WEBrick ve que el path de la petición termina en .cgi la delega a un CGIHandler. Este define las variables de entorno necesarias para el CGI y ejecuta el programa solicitado. Este programa puede afectar al código de estado de la respuesta HTTP devuelta por WEBrick fijando la cabecera "status" al valor deseado.

	jfglez:~$ cat ~jfglez/src/ruby/servlets/cgi-bin/test.410.cgi
	#!/usr/bin/env ruby
	print "status: 410"
	print "Content-type: text/plain\r\n\r\n"
	puts "Cansado. Frustrado. Demasiadas peticiones." +
	  "Me voy a pescar. Vuelvo después de las 17:00 horas"

	[jfglez@localhost docs]$ w3m -dump_extra http://localhost:8080/cgi-bin/test.410.cgi
	W3m-current-url: http://localhost:8080/cgi-bin/test.410.cgi
	W3m-document-charset: US-ASCII
	HTTP/1.1 410 Gone
	Connection: close
	Date: Fri, 15 Apr 2005 08:57:07 GMT
	Content-Type: text/plain
	Server: WEBrick/1.3.1 (Ruby/1.8.1/2003-12-25)
	Content-Length: 93

	Cansado. Frustrado. Demasiadas peticiones. Me voy a pescar. Vuelvo después de las 17:00 Horas

Aviso

Un CGIHandler espera hasta que el proceso CGI finalice. Si este proceso realiza una salida incremental, ésta no se enviará al cliente hasta la finalización del proceso. Se me ha comunicado que cierta persona está intentando realizar otro manejador de CGIs, que envía la salida inmediatamente, para su inclusión en Ruby 1.8.2

3.3. ERBHandler

Así como WEBrick, a través de un FileHandler, es capaz de ejecutar programas CGI si estos tienen la extensión .cgi; también es capaz de ejecutar código Ruby embebido en páginas HTML si éstas tienen la extensión .rhtml.

WEBrick realiza el filtrado de las sentencias Ruby incluidas en la página HTML utilizando por defecto el programa erb. Este programa interpreta como código Ruby las sentencias que se encuentren entre los siguientes delimitadores, dejando el resto sin tocar.

Tabla 1. Delimitadores para embeber código Ruby en páginas HTML

ExpresiónDescripción
<% código ruby %>Ejecuta el código Ruby que se encuentra entre los delimitadores
<%= expresión Ruby %>Evalúa la expresión Ruby, substituyendo la secuencia por el resultado de la expresión
<%# código Ruby %>Comenta una porción de código Ruby, útil en la depuración
% línea de código RubyLa línea se supone que sólo contiene código Ruby
Veamos un ejemplo, primero se crea un fichero con una página HTML que embebe la expresión Ruby, Time.now.

	[jfglez@localhost ~]$ cat ~jfglez/src/ruby/servlets/hello.rhtml
	<html><head>
	<title>ejemplo de erb</title>
	</head>
	<body>
	hola desde ERB a las <%= Time.now %>
	</body>
	</html>
Y a continuación se prepara el servidor que monta un FileHandler en el directorio donde se dejó la página anterior.

#!/usr/local/bin/ruby -w

require 'webrick'
include WEBrick

def start_webrick( config={} )
  config.update( :Port=> 8080 )
  server = HTTPServer.new( config )
  yield server if block_given?
  ['INT','TERM'].each do |signal|
    trap( signal ) { server.shutdown }
  end
  server.start
end #start_webrick

start_webrick do |server|
  doc_root= File.expand_path("~jfglez/src/ruby/servlets")
  server.mount( "/", HTTPServlet::FileHandler, doc_root, 
    { :FancyIndexing=> true } )
end
Al solicitar la página "hello.rhtml", el FileHandler anterior delega el servicio en un ERBHandler que realizará la ejecución de las sentencias y expresiones Ruby que encuentre entre los delimitadores presentados en la tabla anterior, obteniéndose el siguiente resultado:

[jfglez@localhost ~]$ w3m -dump http://localhost:8080/hello.rhtml
   hola desde ERB a las Fri Dec 24 18:21:36 CET 2004
Como puede observarse la expresión Ruby Time.now ha sido sustituida por el resultado de su ejecución dejando el resto de la página inalterado.

3.4. ProcHandler

WEBrick nos permite ser vagos. Si lo que necesitamos es trivial y puede expresarse con un simple Proc o un bloque, entonces no tenemos porque preocuparnos en crear una subclase de AbstractServlet.


    start_webrick do |server|
    server.mount_proc("/myblock") { |req, res|
      res.body= "Un bloque montado en  #{req.script_name}"
    }
  
    my_wonderful_proc = Proc.new {|req,res|
      res.body = " Un maravilloso bloque montado en #{req.script_name}"
    }
  
    server.mount_proc("/myproc",my_wonderful_proc)
    server.mount("/myprochandler",HTTPServlet::ProcHandler.new(my_wonderful_proc))
    end
  
siendo el resultado:

  [jfglez@localhost docs]$ w3m -dump http://localhost:8080/myproc
   Un maravilloso bloque montado en /myproc
  [jfglez@localhost docs]$ w3ms -dump http://localhost:8080/myblock
   Un bloque montado en /myblock
  [jfglez@localhost docs]$ w3m -dump http://localhost:8080/myprochandler
   Un maravilloso bloque montado en /myprochandler