Posts Tagged ‘jdk’

Armando un servlet desde bien abajo.

mayo 12, 2012

Este artículo de hoy no busca ser una receta de cómo hacer algo en el ambiente laboral. Muchas de estas que vamos a ver las resuelven herramientas como Eclipse, maven, etc. El objetivo es un “estudio” un poco más hondo de lo que pasa desde que escribimos nuestro servlet hasta que llega al servidor y queda corriendo.

El servlet lo vengo prometiendo desde un post anterior. La idea de bajar a la línea de comando surgió de la idea de probar esto de los servlets sin tener el Eclipse ni el Netbeans. Así que estábamos linux, vim, la jvm y yo. Comparto la experiencia porque creo que puede ayudar a alguien que trabaja con java web a entender mejor y saber realmente lo que hacen por nosotros las herramientas.

El ejemplo lo vamos a hacer para Windows esta vez.

¿qué vamos a hacer?

Un contador de palabras. El contador de palabra tiene dos componentes: Un formulario en el que escribimos un texto y un servlet que cuenta las palabras y nos muestra por pantalla el resultado.

Nos va a quedar un a archivo palabras.war, que es la manera en que la especificacion de java empaqueta las aplicaciones web. Ese .war va a tener un formulario html y un servlet.

¿qué necesitamos?

Ganas y:

  • un jdk de 1.5 en adelante.
  • un contenedor de servlets, en este caso tomcat 6 , bajarlo como .zip, no vale usar instalador.
  • un editor de texto.
  • un poco de maña con variables de entorno y scripts .bat.

Conociendo el contenedor de Servlets

Tomcat es el contenedor de servlets de uso más extendido, así que no es mal plan conocer algunas cosas básicas. Es una aplicación java, por lo cuál corre desde una JRE. Desde la versión 5, no es necesaria la JDK. Sin embargo como nosotros vamos a compilar y armar nuestro proyecto a mano, vamos a necesitarla de todos modos.

El paquete zip viene con los siguientes directorios.

apache-tomcat-6.x.x/
  bin/
  conf/
  lib/
  logs/
  temp/
  webapps/
  work/

A vuelo de pájaro: el directorio bin contiene los ejecutables y los scripts de arranque y de apagado.

El directorio conf tiene los archivos server.xml, web.xml y tomcat-users.xml como más representativos. Permiten configurar los puertos que va a usar, opciones de la web que vamos a mostrar, como cual es la página de inicio y los usuarios de algunas aplicaciones.

Lib tiene los jar que usa para trabajar y que van a usar en común las aplicaciones que carguemos. Nos puede dar una mano para compilar.

temp maneja los archivos temporales

En webapps van las aplicaciones. El objetivo es generar el palabras.war y ponerlo en este directorio. Tomcat sólo lo va a descomprimir y poner en marcha.

work es un directorio que usa internamente tomcat donde despliega las cosas.

Lo descomprimimos y vamos al directorio bin y ahí ejecutamos startup.sh. Va a aparecer una ventanita que nos va  informar lo que está haciendo. Una vez que termine informa: Server statup in x ms.

Windows probablemente nos informe que está queriendo acceder a la red. Le decimos que sí. Lo que está pasando es que al ser un servidor, Windows nos avisa que estamos abriendo algo que está escuchando cosas en la red y que otras computadoras pueden acceder. En realidad no es nada grave porque lo estamos usando nosotros sólos y hoy por hoy lo más probable es que estemos detrás de un router, así que si alguien accede, es alguien de la red local, con lo cuál no hay un peligro cierto.

Abrimos el navegador y accedemos a http://localhost:8080.

Deberíamos ver una pantalla donde nos comenta que estamos viendo, aparece el logo de tomcat y todo feliz.

Manos a la obra.

Primero vamos a armar un directorio, y dentro de este vamos a tener la siguiente estructura:

./
  src/
    com/
      palabras/
        controller/
  webapp/
    WEB-INF/
    classes/

El código de la aplicación va a estar en los siguientes archivos:

  • src/com/palabras/controller/ContarPalabrasServlet.java
    Clase java que implementa el servlet contador de palabras.
  • webapp/index.html
    Archivo HTML que contiene un formulario que usa al servlet contador de palabras.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
  <HEAD>
    <TITLE>Jugando con palabras</TITLE>
  </HEAD>
  <BODY>
    <H1>Jugando con palabras</H1>
    <P>Aplicaci&oacute;n muy sencilla basada en servlets para contar palabras.</P>
    <FORM METHOD="POST" ACTION="ContarPalabras">
      <FIELDSET>
        <P>Escrib&iacute; o peg&aacute; un texto ac&a abajo:</P>
        <TEXTAREA name="texto" ROWS="5"></TEXTAREA>
        <BR/>
        <INPUT TYPE="SUBMIT" value="A contar!" />
      </FIELDSET>
    </FORM>
  </BODY>
</HTML>

Acá empiezan las complicaciones. Hay que compilar y generar el archivo .war.

De estas tareas normalmente se encargan eclipse, ant o maven. Hoy vamos a tratar de hacerlo nosotros.

Los primeros ejemplos que uno compila en java no usan nada más que las clases propias y las clases que ya vienen en rt.jar, o la biblioteca estándar de runtime. Por eso casi no jugamos con el classpath.

package com.palabras.controller;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class ContarPalabrasServlet extends HttpServlet {

	public ContarPalabrasServlet() {
		super();
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		manejarRequest(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		manejarRequest(request, response);
	}

	private void manejarRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	PrintWriter out = response.getWriter();
		out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
    	out.println("<HTML>\n");
		out.println("\t<HEAD>\n");
    	out.println("\t\t<TITLE>Contar Palabras</TITLE>\n");
    	out.println("\t</HEAD>\n");
    	out.println("\t<BODY>\n");
		out.println("\t\t<H1>Contar Palabras</H1>\n");

		String texto = (String )request.getParameter("texto");
		if (texto == null || texto.isEmpty()) {
			out.println("\t\t<h2 style=\"color:red;\">Debe ingresar un texto, sino no hay mucho que contar.</h2>\n");
		} else {
			out.println(String.format("\t\t<p>El texto se compone de %d palabras</p>", texto.split("/\\s/").length));
			out.println(String.format("\t\t<textarea readonly=\"true\">%s</textarea>", texto));
		}
    	out.println("\t</BODY>\n");
    	out.println("</HTML>\n\n");
    	out.close();
	}

}

Acá la cosa es un poco distinta. Si queremos compilar nuestro Servlet vamos a fracasar lastimosamente porque no encuentra la clase HttpServlet. Esto es porque la clase no está ni entre nuestro ni entre la biblioteca estándar de java. Bueno, esa biblioteca viene incorporada en el directorio lib/ que mencionábamos más arriba, y servlet-api.jar.

Para compilar, entonces vamos a tener que indicar que el classpath incluya el directorio de tomcat/lib con la opcion –classpath o -cp.

Otra cosa que nos va a pasar es que el .class va a quedar en el mismo directorio de la clase, y nosotros queremos que vaya en una ubicación especial respetando la jerarquía de directorios que corresponde al paquete deonde creamos el servlet (com.palabras.controller). El compilador de java nos da una opción -d RUTA para compilar y dejar los .class con la estructura de subdirectorios. Si compilamos el servlet con la opción -d C:\, notaríamos que aparece en C:\ un directorio com, adentro del último uno llamado palabras y así.

¿Pero, a dónde?

Conociendo los archivos war

El war es un archivo comprimido que adentro contiene una serie de archivos y directorios. La organización interna no es caprichosa y responde a una especificación.

Vamos a trabajar con la especificación 2.4. Las más nuevas tienen una serie de simplificaciones que vamos a obviar para retormarlas cuando estemos un poco más duchos.

El war que vamos a armar tiene la siguiente pinta:

./
  WEB-INF/
    classes/
       com/
         palabras/
           controller/
              ContarPalabrasServlet.class
    web.xml
  index.html

El directorio WEB-INF tiene información que la indican al contenedor características del proyecto, las bibliotecas que la aplicación usa y las clases que componen a la aplicación. Nuestro proyecto es tan sencillo que le alcanza sólo con la carpeta classes.

Ahora que sabemos donde, podemos compilar:

javac -cp c:\apache-tomcat-6.0.35\lib\servlet-api.jar \
      -d webapp\WEB-INF\classes \
      src\com\palabras\controller\ContarPalabrasServlet.java

El parámetro -d webapp\WEB-INF\classes es el que le indica que guarde el resultado de compilar en el directorio WEB-INF\classes.

El archivo web.xml es el descriptor de implementación y es el encargado de indicarle al contenedor de servlets cual es la página de inicio, el nombre con el que tiene que desplegar el proyecto, el mapeo de urls que no correspondan a archivos dentro del war. Si nos fijamos en el formulario dentro de index.html hacemos referencia a una action ContarPalabras, pero no existe ningún archivo que se llame así.

Para que tomcat pueda atender al pedido de ContarPalabras tenemos que indicárselo en el web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="palabras" version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<description>
		Jugando con palabras
	</description>
	<display-name>palabras</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
	<servlet>
		<description>
			Servlet para contar palabras
		</description>
		<display-name>ContarPalabras</display-name>
		<servlet-name>ContarPalabrasServlet</servlet-name>
		<servlet-class>com.palabras.controller.ContarPalabrasServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>ContarPalabrasServlet</servlet-name>
		<url-pattern>/ContarPalabras</url-pattern>
	</servlet-mapping>
</web-app>

Bueno, acá hay muchas cosas. El tag welcome-fil-list, contiene una lista de archivos que pueden ser interpretados por el contenedor como archivo por defecto cuando piden la aplicación.

El tag servlet define un servlet, el display-name es el nombre que nos muestra en el servidor, el servlet-name es un identificador del servlet en este contexto y servlet-class es la clase que implementa el servlet. En nuestro caso com.palabras.controller.ContarPalabrasServlet. Hay que poner el nombre completo (FQDN), para que pueda resolverlo buscando en el directorio WEB-INF/classes.

Por último <servlet-mapping> es el que le dice al contenedor que servlet debe resolver que la url del request.

En Java EE 6, la definición y mapeo de servlets se realiza mediante annotations, una de las razones por la cuál el archivo web.xml es optativa.

Ahora que ya tenemos todo armado en el directorio webapp llegó la hora de empaquetarlo y desplegarlo (deployarlo).

Esto se hace usando el comando jar que viene con la jvm. jar nos permite armar paquetes como los .jar, los .war, .ear, etc.

Nos vamos al directorio donde creamos los directorios src y webapp y ejecutamos:

jar cf palabras.war -C webapp .

Nos fijamos en el directorio y tenemos un archivo palabras.war. Podemos abrirlo con WinRar y verificar que tiene lo que esperamos.

Poner en marcha la aplicación será tan difícil como copiar el archivo palabras.war en el directorio webapps dentro del directorio del tomcat.

Tomcat detecta la novedad en el filesystem y realiza el despliegue de la aplicación. Cuando termina, nos lo notifica.

Abrir el navegador e ir a http://localhost:8080/palabras

Si todo fue bien, felicitaciones! Cualquier cosa acá abajo se contestan dudas.

Anuncios