4. La creación de Servlet a medida

4.1. Los métodos do_

Escribir un servlet es bastante sencillo. Primero hay que crear una subclase de HTTPServlet::AbstractServlet. A continuación, dependiendo de si se quiere dar servicio a peticiones GET, POST, OPTIONS o HEAD, añadir respectivamente los métodos do_GET, do_POST, do_OPTIONS o do_HEAD. Si se quiere dar servicio a algunas de las peticiones menos frecuentes , como PUT, sólo hay que crear el correspondiente método do_, e.g. do_PUT.

AbstractServlet ya implementa por nosotros los métodos do_HEAD y do_OPTIONS. El método do_HEAD llama simplemente a do_GET (el cual es necesario proporcionar) y devuelve todo excepto el cuerpo del mensaje HTTP. do_OPTIONS devuelve una lista con los métodos do_ disponibles.

Nos podemos preguntar, "¿Qué es lo que debe hacer un método do_?". Eso depende de cada uno. WEBrick llamará a un método do_ con dos argumentos: los objetos que representan la petición y la respuesta. Lo que se querrá normalmente es interrogar a la petición y definir correspondientemente el objeto respuesta.


       class GreetingServlet < HTTPServlet::AbstractServlet
 	  def do_GET( req, res )
 	    if req.query['name'] then  
 	     res.body = 
 	       "#{@options[0]} #{req.query['name']}. #{@options[1]}"
 	     raise HTTPStatus::OK
 	    else
 	     raise HTTPStatus::PreconditionFailed.new(
 	       "falta el atributo: 'name'"
 	     )
 	    end
	   end 
	   alias do_POST do_GET
	 end #GreetingServlet
 
	 start_webrick do |server|
	   server.mount("/greet",GreetingServlet,"Hola","¿Has tenido un buen dia?")
	 end
    
siendo el resultado:

    [jfglez@localhost docs]$ w3m -dump http://localhost:8080/greet
    Precondition Failed

    falta el atributo: 'name'

    ---------------------------------------------------------------------------

    WEBrick/1.3.1 (Ruby/1.8.1/2003-12-25) at localhost:8080
    [jfglez@localhost docs]$ w3m -dump http://localhost:8080/greet?name=jfglez
    Hola jfglez. ¿Has tenido un buen día?
    

4.2. La respuesta

Existen dos formas de fijar el código de estado de la respuesta, La primera, como se a mostrado en el punto anterior, es lanzar una excepción HTTPStatus. Recomiendo este método porque, en el caso de un código de estado erróneo, se devuelve una página HTML con una traza. Si es necesario proporcionar una página de error a medida,

  1. Establecer manualmente el código de estado y el cuerpo de la respuesta, o

  2. Extender la clase HTTPResponse con el método create_error_page al que se llamará en caso de error.

a mi me gusta más el primer método dado que no se accede a la excepción generada desde el interior del método create_error_page


class GreetingWithCustomizedErrorPageServlet < HTTPServlet::AbstractServlet
  def do_GET( req, res )
    if req.query['name'] then
      res.body= "#{@options[0]} #{req.query['name']}, #{@options[1]}"
      raise HTTPStatus::OK
    else
      res.status= 412
      res.body= "Error dentro de #{self.class}"
      res['content-type']= 'text/plain'
    end
  end #do_GET
end # GreetingWithCustomizedErrorPageServlet

class GreetingWithExtendedResponseObjectServlet < HTTPServlet::AbstractServlet
  def do_GET( req, res )
    # Extendemos el objeto res
    class << res
      def create_error_page
        # 'content-type' por defecto es 'text/html'
        self['content-type']= 'text/plain'
        self.body= "Error dentro de " +
             "GreetingWithExtendedResponseObjectServlet"
        # el código de estado se determina a partor de la
        # excepción HTTPStatus que se produzca
      end #create_error_page
    end 

    raise HTTPStatus::PreconditionFailed unless req.query['name']
    res.body= "#{@options[0]} #{req.query['name']}, #{@options[1]}"
    raise HTTPStatus::OK
  end # do_GET
end # GreetingWithExtendedResponseObjectServlet

start_webrick do |server|
  server.mount('/greet1',GreetingWithCustomizedErrorPageServlet,
               'Hi','¿Qué tal día estas teniendo?')
  server.mount('/greet2',GreetingWithExtendedResponseObjectServlet,
               'Hi','¿Qué tal día estas teniendo?')
end

      
Siendo el resultado:

[jfglez@localhost docs]$ w3m -dump http://localhost:8080/greet1
   Error dentro de GreetingWithCustomizedErrorPageServlet
[jfglez@localhost docs]$ w3m -dump http://localhost:8080/greet2
   Error dentro de GreetingWithExtendedResponseObjectServlet
Pero, ¿Qué excepciones HTTPStatus tenemos disponibles?. Muchas; La siguiente tabla muestra las posibles excepciones:

Tabla 2. Excepciones HTTPStatus

CódigoExcepción
100Continue
101SwitchingProtocols
200OK
201Created
202Accepted
203NonAuthoritativeInformation
204NoContent
205ResetContent
206PartialContent
300MultipleChoices
301MovedPermanently
302Found
303SeeOther
304NotModified
305UseProxy
307TemporaryRedirect
400BadRequest
401Unauthorized
402PaymentRequired
403Forbidden
404NotFound
405MethodNotAllowed
406NotAcceptable
407ProxyAuthenticationRequired
408RequestTimeout
409Conflict
410Gone
411LengthRequired
412PreconditionFailed
413RequestEntityTooLarge
414RequestURITooLarge
415UnsupportedMediaType
416RequestRangeNotSatisfiable
417ExpectationFailed
500InternalServerError
501NotImplemented
502BadGateway
503ServiceUnavailable
504GatewayTimeout
505HTTPVersionNotSupported
El cuerpo de una respuesta no tiene porque ser necesariamente un objeto String, también se le puede pasar un objeto IO. Este puede ser de mucha utilidad si la respuesta es muy larga, e.g. hay que devolver el contenido de un fichero de 16MB de longitud.

4.3. Controlando el número de objetos de un servlet

Existen ciertos momentos en los que no se desea que WEBrick cree automáticamente un nuevo objeto de un servlet. Por ejemplo, si la parte de inicialización del servlet es muy costosa, es preferible reusar el mismo objeto o por lo menos gestionar una agrupación de objetos.

WEBrick llama al método de clase get_instance con los parámetros config y options. Este método debe devolver un objeto que WEBrick pueda utilizar para dar servicio a la petición. Es recomendable colocar un mutex que controle la sección crítica dado que ahora se puede acceder al mismo objeto desde más de un hilo de ejecución a la vez.


require 'thread'

class CounterServlet < HTTPServlet::AbstractServlet
  @@instance= nil
  @@instance_creation_mutex= Mutex.new
  def self.get_instance( config, *options )
    @@instance_creation_mutex.synchronize {
      @@instance= @@instance || self.new( config, *options )
    }
  end #get_instance

  attr_reader :count
  attr :count_mutex

  def initialize( config, starting_count )
    super
    @count= starting_count
    @count_mutex= Mutex.new
  end #initialize

  def do_GET( req, res )
    res['content-type']= 'text/plain'
    @count_mutex.synchronize {
      res.body= @count
      @count+= 1
    }
  end # do_GET
end # CounterServlet

start_webrick do |server|
  server.mount( '/count_from_0', CounterServlet, 0 )
  # 100 no tiene efecto
  server.mount( '/count_from_0_too', CounterServlet, 100 )
end
Siendo el resultado:

[jfglez@localhost docs]$ w3m -dump http://localhost:8080/count_from_0
0
[jfglez@localhost docs]$ w3m -dump http://localhost:8080/count_from_0
1
[jfglez@localhost docs]$ w3m -dump http://localhost:8080/count_from_0
2
[jfglez@localhost docs]$ w3m -dump http://localhost:8080/count_from_0
3
[jfglez@localhost docs]$ w3m -dump http://localhost:8080/count_from_0_too
4
[jfglez@localhost docs]$ w3m -dump http://localhost:8080/count_from_0_too
5

4.4. Cookies

Eric Hodel ha permitido gentilmente reproducir aquí su artículo sobre la utilización de cookies en WEBrick en beneficio de los lectores de copias en papel. También se ha copiado la estructura de WEBrick::Cookies en la sección de referencia.

4.4.1. WEBrick y las Cookies de Eric Hodel

WEBrick expone las cookies a través de una clase sencilla y de fácil uso Cookie que además sigue la RFC 2109 sobre cookies. Los objetos HTTPRequest y HTTPRespomse permiten leer y establecer las cookies de una forma muy sencilla.

(las cookies son delicadas delicias)

La clase WEBrick::Cookie arropa una cookie y expone sus propiedades. En WEBrick una cookie se crea a través de WEBrick::Cookie.new a la vez que se le da un nombre y un valor. Una vez creado un objeto cookie se puede acceder a sus propiedades con los siguientes métodos (Las descripciones corresponden a la RFC 2109 y de las especificaciones sobre Cookies de Netscape):

name

El nombre de la cookie. Este sólo se puede leer no se puede modificar.

value

El valor de la cookie, debe venir codificado en ASCII imprimible.

version

Identifica la especificación que sigue la cookie. 0, el valor por defecto, para las Cookies de Netscape y 1, para las cookies que siguen la RFC 2109.

domain

El dominio en el cual es válida la cookie. Una especificación explícita de un dominio debe comenzar siempre por punto.

expires

Un objeto Time o String que representa el momento en el que expira la cookie. Siempre debe tener el siguiente formato: DiaSem, DD-Mes-AAAA HH.MM.SS GMT

max_age

El tiempo de vida de la cookie en segundos desde el momento en que se envía. Un valor cero indica que se debe descartar inmediatamente.

comment

Permite al servidor de origen documentar el uso que se pretende dar a la cookie. El usuario puede inspeccionar esta información y decidir si iniciar o continuar una sesión con esta cookie.

path

El subconjunto de la URL a la que se aplica la cookie.

secure

Al poner a true, la cookie debe devolverse al origen sólo a través de una conexión segura.

Las cookies las lee automáticamente WEBrick::HTTPRequest y se guardan en un Array al que se puede acceder través de la propiedad HTTPRequest#cookies. En la respuesta se pueden añadir al Array HTTPResponde#cookies una vez creado un objeto WEBrick::HTTPResponse.

Las cookies no se copian automáticamente del objeto HTTPRequest al objeto HTTPResponse. Esto hay que hacerlo explícitamente.