sábado, 28 de noviembre de 2015

Aplicaciones Web Java usando Gradle y el plugin Jetty

Introducción

En un post anterior se mostró cómo es posible desarrollar un API REST con Java usando Gradle y Jetty embebido. En esta ocasión se desarrollará una aplicación web un poco más tradicional, con un Serlvet y un JSP y teniendo como archivo final de la aplicación un WAR, también usando Java y Gradle.

Dado que para este proyecto se tendrá un archivo WAR, el cual se despliega (deploy) en un Contenedor Web (Web Container o Servlet Container), no se usará Jetty embebido de la misma manera que se usó en aquel post anterior sino que se usará el plugin oficial de Gradle Jetty, el cual sirve para desplegar la aplicación localmente y probarla a medida que se está desarrollando, es decir sólo se usa para pruebas durante el desarrollo.

Dicho plugin tiene una restricción y es que sólo soporta Jetty 6 (a la fecha Jetty se encuentra en la versión 9). Como lo indica la documentación de Jetty, la versión 6 sólo soporta la especificación Servlet 2.5 y JSP 2.0, por lo cual los proyectos que usen este plugin deben usar esas mismas versiones de estas especificaciones.

Para desarrollo de aplicaciones con especificaciones actuales pueden usarse alternativas como Gretty.

El proyecto completo se puede descargar desde: https://github.com/guillermo-varela/webapp-gradle-jetty-example

Dependencias y Configuración Gradle

build.gradle

plugins {
  id 'net.researchgate.release' version '2.0.2'
}

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse-wtp'
apply plugin: 'jetty'

sourceCompatibility = 1.7
targetCompatibility = 1.7

task wrapper(type: Wrapper) {
    gradleVersion = '2.8'
}

repositories {
    jcenter()
}

dependencies {
    providedCompile "javax.servlet:servlet-api:2.5"
    providedCompile "javax.servlet:jsp-api:2.0"
    providedCompile "javax.el:el-api:2.2"
    compile "jstl:jstl:1.2"
}

httpPort=8080
stopPort=9090
stopKey="stopKey"

En la línea 6 se aplica el plugin war, el cual:

  • Hace que el archivo generado luego de compilar el proyecto sea un WAR dentro de "build/libs".
  • Permite que el contenido de la carpeta "src/main/webapp/" haga parte del proceso de construcción y se toma como el contenido web (páginas, estilos CSS, Javascript, imágenes, etc.). Nota: el plugin no crea dicha carpeta, debe ser creada manualmente.
  • Adiciona las configuraciones "providedCompile" y "providedRuntime" para la administración de dependencias, las cuales permiten incluir dependencias que se requieren para la compilación del proyecto pero no requieren incluirse dentro del WAR final ya que el Contenedor Web se encarga de proveerlas.
En la línea 7 se aplica el plugin eclipse-wtp, el cual es completamente opcional (aún si se está usando Eclipse) y se usa para indicarle al IDE que se trata de un proyecto web y debe usar "Web Tools Platform".

En la línea 8 se aplica el plugin jetty, el cual permite desplegar la aplicación web en una instancia embebida de Jetty durante el desarrollo.

Entre las líneas 21 y 26 se indican las dependencias del proyecto web. En este punto sólo se incluye la dependencia de JSTL para que haga parte de la compilación y generación del WAR ya que los contenedores no suelen incluir esta librería, aunque ello debe confirmarse según el contenedor a usar en los ambientes de producción.

Entre las líneas 28 y 30 se indica el puerto en el cual funcionará Jetty (httpPort), el puerto hacia el cual se enviará el comando para detener su ejecución (stopPort) y la contraseña o llave para detener la ejecución (stopKey).

Más información sobre estos parámetros: https://wiki.eclipse.org/Jetty/Howto/Secure_Termination

gradle.properties

version=1.0.0-SNAPSHOT

Servlet

ExampleServlet.java

Simplemente pondrá un objeto Date en la petición procesada e indicará que se muestre la página "example.jsp"

package com.blogspot.nombre_temp.webapp.gradle.jetty.example;

import java.io.IOException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ExampleServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("currentDate", new Date());
        request.getRequestDispatcher("example.jsp").forward(request, response);
    }
}

Contenido Web

Como se indicó anteriormente, se debe crear manualmente la carpeta "src/main/webapp/" y a su vez dentro de esta se crearán los siguientes archivos:

index.jsp

Será una página sencilla para verificar que la aplicación está funcionando.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false" %>
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title>Webapp Gradle Jetty Example</title>
</head>

<body>
    <h1>
        Hello World
    </h1>
</body>
</html>

Nota: el atributo "session" en la primera línea es opcional y simplemente se deja para ilustrar cómo es posible tener páginas JSP que no creen una sesión HTTP cuando no es necesario, como en este caso.

example.jsp

Mostrará la fecha y hora a partir del objeto Date enviado por "ExampleServlet" usando el formato indicado en el tag JSTL.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" session="false" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Webapp Gradle Jetty Example</title>
</head>

<body>
    <h1>
        Current Date: <fmt:formatDate value="${currentDate}" pattern="yyyy-MM-dd HH:mm:ss" />
    </h1>
</body>
</html>

WEB-INF/web.xml

Descriptor de Despliegue (Deployment Descriptor) con la configuración web de la aplicación, requerido al no tener soporte para Servlet 3.0.

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>Example</servlet-name>
        <servlet-class>com.blogspot.nombre_temp.webapp.gradle.jetty.example.ExampleServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Example</servlet-name>
        <url-pattern>/example</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Entre las líneas 8 y 16 se está declarando el Servlet "ExampleServlet" y se indica que recibirá peticiones que lleguen a la ruta "/example", por ejemplo http://localhost:8080/webapp-gradle-jetty-example/example.

Entre las líneas 18 y 20 se indica que en caso de recibir una petición en la cual no se indique una página específica ni tampoco esté relacionada con una ruta de un Serlvet, se debe buscar un archivo "index.jsp" y mostrarlo al usuario en caso de que exista.

Nota: quienes usan Eclipse y aplicaron el plugin "eclipse-wtp" debe asegurarse de que la carpeta "src/main/webapp/" sea incluida en "Deployment Assembly", lo cual puede hacerse manualmente haciendo clic derecho en el proyecto y seleccionando "Properties" -> "Deployment Assembly" o actualizando el proyecto Gradle usando "Buildship" o "Gradle Integration for Eclipse". En caso de que aparezcan errores cargando el "Deployment Descriptor" (web.xml), basta con cerrar el proyecto y abrirlo de nuevo en Eclipse.

Figura 1 - Deployment Assembly con la carpeta webapp

Ejecutar la Aplicación

Desde Eclipse

Quienes usan Eclipse y aplicaron el plugin "eclipse-wtp" pueden desplegar la aplicación en un servidor configurado en Eclipse.

En Eclipse para configurar un servidor se debe usar el menú "Window" -> "Preferences" -> "Server" -> "Runtime Environmets".

Figura 2 - Runtime Enviroments en Eclipse

Figura 3 - Seleccionando un servidor

Figura 4 - Adicionando Tomcat a Eclipse

Una vez adicionando el servidor en Eclipse, este queda disponible en la vista "Servers" desde donde se puede hacer clic derecho, seleccionar "Add and Remove" y escoger el proyecto.

Figura 5 - Vista Servers en Eclipse

Figura 6 - Adicionando proyectos en el servidor

Después de adicionar el proyecto al servidor, este puede ejecutarse o depurarse (debug) con las opciones "Run" y "Debug" de la vista "Servers".

Figura 7 - Opciones para ejecutar la aplicación desde Eclipse

Desde Jetty

El plugin Jetty de Gradle no sólo permite ejecutar la aplicación luego de ejecutar todas las tareas de Gradle necesarias, sino que también hace que no se dependa de que cada desarrollador instale y configure un servidor localmente, y simplemente al ejecutar la tarea "jettyRun" se ejecutan todas las tareas de Gradle y se despliega el proyecto en un Jetty embebido.

./gradlew jettyRun
:compileJava
:processResources
:classes
> Building 75% > :jettyRun > Running at http://localhost:8080/webapp-gradle-jetty-example

También se tiene la tarea "jettyRunWar" y la diferencia es que la primera ejecuta el proyecto desde una carpeta descomprimida mientras la segunda lo hace generando un WAR primero.

Una funcionalidad adicional de este plugin es que los cambios que se realicen en los archivos de "src/main/webapp/" se ven reflejados automáticamente, sin necesidad de volver a compilar y relanzar las tareas de Gradle. Este comportamiento puede modificarse con las propiedades "reload" y "scanIntervalSeconds" del plugin.

Accediendo a la Aplicación

Al acceder a la URL en la que se indica se está ejecutando el proyecto (http://localhost:8080/webapp-gradle-jetty-example) puede verse el mensaje de bienvenida que se dejó en "index.jsp" así como también en los headers HTTP de respuesta puede comprobarse que se está usando Jetty 6.

Figura 8 - Página inicial señalando la versión de Jetty

Ingresando a http://localhost:8080/webapp-gradle-jetty-example/example puede verse que también se ejecuta el Servlet que pone el objecto Date como atributo y el JSP que lo muestra con el formato indicado.

Figura 9 - JSP con la hora actual desde el Servlet

Deteniendo la Aplicación

Cuando la aplicación se ejecuta desde un servidor configurado en Eclipse se tienen dos maneras de detener la aplicación:

  1. La opción "Stop the server" en la vista "Servers".
  2. La opción "Terminate" en la vista "Console".

Si la aplicación se ejecuta desde el Jetty del plugin de Gradle se debe ejecutar la tarea "jettyStop", bien sea desde la línea de comandos (o terminal) o desde la vista "Gradle Tasks" que proporcionan las integraciones para Eclipse (en caso de usarlas).

Depurando la Aplicación (Debug)

En caso de usar un servidor configurado en Eclipse, puede usarse la opción "Start the server in debug mode" en la vista "Servers" o hacer clic derecho en el servidor y seleccionar "Debug", como se señala en la figura 7.

Si se está usando el plugin Jetty de Gradle, infortunadamente las tareas "jettyRun" o "jettyRunWar" no tienen la posibilidad de incluir el parámetro "--debug-jvm" que sí tiene el plugin "java", por lo cual es necesario crear la variable de entorno en sistema operativo "GRADLE_OPTS" con el valor "-Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n".

Ello permitirá que al ejecutar "jettyRun" o "jettyRunWar" se pueda configurar la depuración remota, como se indica en la sección "Depurar una aplicación Java (Debug)" del post sobre "Gradle Integration for Eclipse" o sobre "Buildship".

Generando el WAR

Una vez la aplicación esté lista para desplegarse en un Contenedor Web, el archivo WAR se genera ejecutando la tarea de Gradle "build", lo cual creará el archivo en la carpeta "build/libs".

./gradlew build
:compileJava
:processResources
:classes
:war
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build

BUILD SUCCESSFUL

Por defecto el nombre del archivo tendrá la estructura: nombre-proyecto-version.war, por lo que en este caso sería: webapp-gradle-jetty-example-1.0.0-SNAPSHOT.war.

En caso de requerir un nombre diferente para el archivo generado puede adicionar algo como lo siguiente a "build.gradle":

war {
    archiveName 'example.war'
}

Conclusiones

El plugin Jetty de Gradle permite se puedan desarrollar aplicaciones web, teniendo mayor independencia en cuanto al ambiente de desarrollo ya que no se requiere que cada desarrollador instale y configure un servidor localmente y pueda usar el IDE de preferencia.

Sin embargo es notorio que el plugin no ha sido lo suficientemente evolucionado con el paso del tiempo, prueba de ello es que sólo soporta Jetty 6. Es por ello que en el siguiente post se mostrará como usar el plugin Gretty, el cual no sólo soporta nuevas versiones de Jetty sino también Tomcat y otras funcionalidades adicionales.

Referencias

No hay comentarios.:

Publicar un comentario