Implementación desde cero de Tomcat - 07 - Cómo analizar y procesar paquetes WAR de terceros

Introducción

En mi experiencia diaria trabajo frecuentemente con servidores web como Tomcat, pero siempre he tenido una comprensión superfciial de su funcionamiento interno.

Por esta razón, decidí implementar mi propia versión simplificada de Tomcat para aprender los conceptos fundamentales de este servidor de aplicaciones.

Serie de tutoriales

Implementación de Apache Tomcat desde cero - 01 - Introducción

Implementación de Apache Tomcat desde cero - 02 - Introducción detallada a web.xml

Implementación de Apache Tomcat desde cero - 03 - Implementación básica de socket

Implementación de Apache Tomcat desde cero - 04 - Abstracción de solicitudes y respuestas

Implementación de Apache Tomcat desde cero - 05 - Soporte para procesamiento de servlets

Implementación de Apache Tomcat desde cero - 06 - Procesamiento de servlets bio/thread/nio/netty con pooling

Implementación de Apache Tomcat desde cero - 07 - Cómo analizar y procesar paquetes WAR de terceros

Implementación de Apache Tomcat desde cero - 08 - Cómo integrar Tomcat con SpringBoot

Implementación de Apache Tomcat desde cero - 09 - Clase manipuladora de servlets

Implementación de Apache Tomcat desde cero - 10 - Archivos de recursos estáticos

Implementación de Apache Tomcat desde cero - 11 - Filtros

Implementación de Apache Tomcat desde cero - 12 - Listeners

Contexto

Hasta ahora, hemos estado trabajando únicamente con nuestros propios servlets.

Sin embargo, un contenedor web como Tomcat debe ser capaz de analizar y procesar paquetes WAR externos.

¿Cómo podemos implementar esta funcionalidad?

  1. Estructura de un paquete WAR ============

Código fuente

Utilicemos un proyecto web simple como ejemplo.

https://github.com/houbb/servlet-webxml

Estructura del proyecto

mvn clean
tree /f

D:.
│
└─src
    └─main
        ├─java
        │  └─com
        │      └─github
        │          └─houbb
        │              └─servlet
        │                  └─webxml
        │                          IndexServlet.java
        │
        └─webapp
            │  index.html
            │
            └─WEB-INF
                    web.xml


pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.houbb</groupId>
    <artifactId>servlet-webxml</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <plugin.tomcat.version>2.2</plugin.tomcat.version>
    </properties>

    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>9.0.0.M8</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>servlet</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>${plugin.tomcat.version}</version>
                <configuration>
                    <port>8080</port>
                    <path>/</path>
                    <uriEncoding>${project.build.sourceEncoding}</uriEncoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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">

    <!--Página de bienvenida por defecto-->
    <welcome-file-list>
        <welcome-file>/index.html</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>index</servlet-name>
        <servlet-class>com.github.houbb.servlet.webxml.IndexServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>index</servlet-name>
        <url-pattern>/index</url-pattern>
    </servlet-mapping>

</web-app>

index.html

<html>
<body>
¡Hola Servlet!
</body>
</html>

Servlet

package com.github.houbb.servlet.webxml;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Servlet de ejemplo para la página de inicio
 */
public class IndexServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html");

        // La lógica real se encuentra aquí
        PrintWriter out = resp.getWriter();
        out.println("<h1>Índice del servlet</h1>");
    }

}

Estructura del directorio

Empaquetamos como WAR y descomprimimos:

mvn clean install

El elemento más importante es web.xml, que sirve como punto de entrada para todo.

Estructura del WAR resultante:

D:.
│  index.html
│
├─META-INF
│  │  MANIFEST.MF
│  │
│  └─maven
│      └─com.github.houbb
│          └─servlet-webxml
│                  pom.properties
│                  pom.xml
│
└─WEB-INF
    │  web.xml
    │
    └─classes
        └─com
            └─github
                └─houbb
                    └─servlet
                        └─webxml
                                IndexServlet.class


  1. ¿Cómo cargar clases berdasarkan la ruta? Las clases no son del proyecto actual =====================

JVM-09-classloader

Implementación principal

package com.github.houbb.minicat.support.classloader;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * Referencias:
 * https://www.liaoxuefeng.com/wiki/1545956031987744/1545956487069728
 *
 * Cada directorio tiene su propio ClassLoader independiente.
 */
public class WebAppClassLoader extends URLClassLoader {

    private Path rutaClases;
    private Path[] jarsLibreria;

    public WebAppClassLoader(Path rutaClases, Path rutaLib) throws IOException {
        super(construirUrls(rutaClases, rutaLib), ClassLoader.getSystemClassLoader());
        
        this.rutaClases = rutaClases.toAbsolutePath().normalize();
        if(rutaLib.toFile().exists()) {
            this.jarsLibreria = Files.list(rutaLib)
                .filter(p -> p.toString().endsWith(".jar"))
                .map(p -> p.toAbsolutePath().normalize())
                .sorted()
                .toArray(Path[]::new);
        }
    }

    static URL[] construirUrls(Path rutaClases, Path rutaLib) throws IOException {
        List<URL> urls = new ArrayList<>();
        urls.add(convertirDirURL(rutaClases));

        // lib puede no existir
        if(rutaLib.toFile().exists()) {
            Files.list(rutaLib)
                .filter(p -> p.toString().endsWith(".jar"))
                .sorted()
                .forEach(p -> {
                    urls.add(convertirJarURL(p));
                });
        }

        return urls.toArray(new URL[0]);
    }

    static URL convertirDirURL(Path p) {
        try {
            if (Files.isDirectory(p)) {
                String abs = obtenerRutaAbsoluta(p);
                if (!abs.endsWith("/")) {
                    abs = abs + "/";
                }
                return URI.create("file://" + abs).toURL();
            }
            throw new IOException("La ruta no es un directorio: " + p);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    static URL convertirJarURL(Path p) {
        try {
            if (Files.isRegularFile(p)) {
                String abs = obtenerRutaAbsoluta(p);
                return URI.create("file://" + abs).toURL();
            }
            throw new IOException("La ruta no es un archivo JAR: " + p);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    static String obtenerRutaAbsoluta(Path p) throws IOException {
        return p.toAbsolutePath().normalize().toString().replace('\\', '/');
    }

}

Repositorio del proyecto

 /\_/\  
( o.o ) 
 > ^ <


mini-cat es una implementación simplifiacda de Tomcat. También conocido como 【嗅虎】(Tener un tigre en el corazón, oler suavemente las rosas.)

Repositorio: https://github.com/houbb/minicat

Etiquetas: java tomcat servlet war web-container

Publicado el 9-21 18:34