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?
- 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
- ¿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