Este es un proyecto que se creó como parte de un curso que tomé en la Universidad de Varsovia. Es una implementación simple de un contenedor de servlet que admite Servlets Java que cumple con Java Servlet API. Aunque es bastante básico, puede cargar y ejecutar aplicaciones simples que utilizan funcionalidades básicas de servlets, incluidos los servlets de envío, JSP y Async.
No encontré muchas otras pequeñas implementaciones de contenedores de servlet, así que siéntase libre de usar esto como un ejemplo de cómo crear un contenedor como este. Tenga en cuenta que, debido a las restricciones de tiempo, es un proyecto bastante imperfecto y buggy y, aunque traté de seguir siendo fiel a la API de servlet, me tomé algunas libertades aquí y allá.
La idea es que este contenedor funcione de manera similar al de Tomcat. Puede agregar clases de servlet usando ServletContainer :: AddRoute, pero la forma preferida es usar la funcionalidad de escaneo de componentes. Es decir, si la aplicación se amplía en el archivo warName.war y se coloca en el directorio deploy dentro server/src/main/resources se cargará en el inicio del servidor y disponible en localhost:8000/warName .
Por defecto, el servidor se ejecuta en el puerto 8000. Se puede cambiar en la función Main . Esta función también contiene y ejemplo de cómo iniciar y configurar este servidor.
El proyecto fue construido con Gradle. Contiene dos subprojects: aplicación de servidor y demostración (base de datos de libros simple). Fue probado en Linux y no sé si incluso funciona en Windows/MacOS.
Para ejecutar el uso del servidor:
./gradlew server:run
Iniciar servidor requiere que creemos una instancia de ServletContainer .
ServletContainer(int threads) Crea una nueva instancia de ServletContainer . con un número dado de hilos utilizados para crear Threadpool. Este grupo se utilizará para manejar las solicitudes HTTP.
void ServletContainer::start(int port) Comienza el contenedor de servlet en el puerto dado. Tenga en cuenta que los archivos .War no se cargan en este punto. Solo se cargan cuando se llama ServletContainer:servletScan .
void ServletContainer::stop()Detiene con gracia el contenedor.
void ServletContainer::servletScan() Carga todos los archivos .war desde el directorio deploy dentro server/src/main/resources . También cargará los archivos .jsp e inmediatamente los traspilará a archivos .class. Solo se cargarán clases anotadas con @WebServlet . Una advertencia es que los archivos .war que uso tienen una estructura de directorio específica que no estoy tan seguro es lo mismo que en la mayoría de los otros archivos .war. Puede verificar la tarea de gradle war en la aplicación de demostración para ver cómo debería ser esta estructura. Este método debe llamarse como máximo una vez antes del método Start ().
void ServletContainer::servletScan(String url) Igual que ServletContainer::servletScan() pero puede cargar archivos .war desde URL dada.
void ServletContainer::addRoute(Class<? extends HttpServlet> cls, String path)Agrega servlet al contenedor. Servlet solo manejará la URL dada (ver Preguntas frecuentes para obtener más detalles).
El servidor admite el manejo concurrente de múltiples clientes a la vez. Cada cliente será manejado por hilo separado de Threadpool. El tamaño de grupo predeterminado es 4 y se puede cambiar en la función Main .
El servidor admitirá cualquier clase que herede de httpservlet. Tal clase se puede agregar usando ServletContainer::addRoute .
Se implementan las funcionalidades más importantes de estas clases. Puede escribir al cliente usando (solo) PrintWriter , establecer encabezados y estado de respuesta. HttpServletRequest puede extraer la URL de solicitud, el método HTTP (obtener, publicar, eliminar, parche), parámetros de consulta y parámetros del cuerpo (en el caso de la publicación). Los datos se envían al cliente después de enjuagar el búfer o cerrar el HttpServletResponse . Usando RequestDispatcher , puede enviar consultas a otros servlets, incluidos los servlets JSP.
Hay apoyo para los servlets async. Después de ejecutar HttpServletRequest::startAsync , la solicitud entra en modo asíncrono. Hay dos formas de usar este modo. Cualquier código se ejecutará siempre que AsyncContext::complete se ejecuta en algún momento, lo que termina la conexión al cliente. También puede usar AsyncConext::start(Runnable) , aquí también debe ejecutar AsyncContext::complete en algún momento. Este último es compatible con la API Java Servlet. También es compatible con TimeOut ( AsyncContext::setTimeout ) y AsyncListener . Los servlets async se implementan utilizando CompletableFuture para que usen Threadpool separado del que se usa para clientes sincrónicos.
Si la aplicación se ha adherido a .war y se mudó a la carpeta deploy , se cargará automáticamente. Todas las clases anotadas con @WebServlet y heredar desde HttpServlet se agregarán al contenedor de servlet y estarán disponibles en localhost:port/warName/servletUrl . Cada clase debe tener exactamente un servletUrl especificado en la anotación @WebServlet en el atributo de valor. Si la aplicación usa JSP, se compilará automáticamente en .class y cargar. Muchas aplicaciones se pueden cargar, sin embargo, no sé qué sucede cuando hay los mismos nombres de clase utilizados en dos aplicaciones.
El servidor puede transpionar los archivos .jsp a .class. Hay soporte para casi toda la sintaxis en JSP. Eso incluye:
<%@ page import/include=... %><% ... %><%! ... %><%= ... %><%-- %>${...} ${} La sintaxis admite parcialmente el lenguaje de expresión. Se pueden realizar operaciones aritméticas simples, y cualquier expresión de la instance.property1.property2 de formulario request.getAttribute("instance").getProperty1().getProperty2() En <% ... %> también hay out.println(...) que escribe directamente al cliente. JSP se puede mostrar utilizando RequestDispatcher::forward o está disponible directamente en localhost:8000/warName/jspFileName.jsp . Una acción de muestra JSP está disponible en localhost:8000/library/jsp . Tenga en cuenta que implementé analizándome para que el formato de código extraño/sintaxis pueda romperlo.
Para demostrar el contenedor de servlet, implementé una aplicación simple que simula una base de datos de libros. Los libros se pueden agregar, eliminar y actualizar utilizando formularios HTML. Todo el libro también se puede ver en la tabla HTML. El frontend está completamente desarrollado usando JSP. La aplicación se adhiere a la biblioteca. War utilizando la tarea de gradle war , se movió para deploy y cargada en el servidor cuando se inicia.
Puntos finales:
Hay más de 30 pruebas para verificar la mayor parte de la funcionalidad y la aplicación de demostración. Se prefiere ejecutarlos desde IntelliJ debido a los problemas explicados en las preguntas frecuentes . Alternativamente, se pueden ejecutar usando:
./gradlew test
Este proyecto fue parte de un curso universitario y no se me permitió usar bibliotecas de terceros, excepto Junit.
Hay problemas con los puertos de socket que ya están en uso. Nunca me puse para arreglarlo, pero la mayoría de las veces funciona cuando se me ejecuta desde IntelliJ.
En este momento, la resolución de qué servlet debe manejar la URL dada es bastante primitiva. Básicamente, el servidor url dado busca un servlet cuyo servletUrl es un prefijo de url . Si hay muchos con el prefijo más largo se selecciona. Esto significa que si un servlet tiene servletUrl igual a /app/home también manejará /app/home/nonexisting/ , /app/home/12345 etc.
Cualquiera es bienvenido a contribuir a este proyecto. Creé algunos problemas de buen tema, así que siéntase libre de verlos :)