La forma más sencilla de servir datos estáticos desde fuera del servidor de aplicaciones en una aplicación web Java

10 minutos de lectura

Avatar de usuario de Janne
jane

Tengo una aplicación web Java ejecutándose en Tomcat. Quiero cargar imágenes estáticas que se mostrarán tanto en la interfaz de usuario web como en archivos PDF generados por la aplicación. También se agregarán y guardarán nuevas imágenes cargándolas a través de la interfaz de usuario web.

No es un problema hacer esto teniendo los datos estáticos almacenados dentro del contenedor web, pero almacenarlos y cargarlos desde fuera del contenedor web me está dando dolor de cabeza.

Preferiría no usar un servidor web separado como Apache para servir los datos estáticos en este momento. Tampoco me gusta la idea de almacenar las imágenes en binario en una base de datos.

He visto algunas sugerencias, como que el directorio de imágenes sea un enlace simbólico que apunte a un directorio fuera del contenedor web, pero ¿funcionará este enfoque tanto en entornos Windows como * nix?

Algunos sugieren escribir un filtro o un servlet para manejar el servicio de imágenes, pero esas sugerencias han sido muy vagas y de alto nivel sin indicadores de información más detallada sobre cómo lograr esto.

Avatar de usuario de BalusC
BalusC

He visto algunas sugerencias, como que el directorio de imágenes sea un enlace simbólico que apunte a un directorio fuera del contenedor web, pero ¿funcionará este enfoque tanto en entornos Windows como * nix?

Si cumple con las reglas de ruta del sistema de archivos *nix (es decir, usa exclusivamente barras inclinadas como en /path/to/files), entonces también funcionará en Windows sin la necesidad de jugar con feos File.separator concatenaciones de cadenas. Sin embargo, solo se escanearía en el mismo disco de trabajo desde donde se invocó este comando. Entonces, si Tomcat está, por ejemplo, instalado en C: entonces el /path/to/files en realidad apuntaría a C:\path\to\files.

Si todos los archivos están ubicados fuera de la aplicación web y desea tener los archivos de Tomcat DefaultServlet para manejarlos, entonces todo lo que básicamente necesita hacer en Tomcat es agregar el siguiente elemento Contexto a /conf/server.xml adentro <Host> etiqueta:

<Context docBase="/path/to/files" path="/files" />

De esta manera serán accesibles a través de http://example.com/files/.... Para servidores basados ​​en Tomcat como JBoss EAP 6.x o anteriores, el enfoque es básicamente el mismo, consulte también aquí. El ejemplo de configuración de GlassFish/Payara se puede encontrar aquí y el ejemplo de configuración de WildFly se puede encontrar aquí.

Si desea tener control sobre la lectura/escritura de archivos usted mismo, debe crear un Servlet para esto que básicamente solo obtiene un InputStream del archivo en sabor de por ejemplo FileInputStream y se lo escribe al OutputStream del HttpServletResponse.

En la respuesta, debe configurar el Content-Type encabezado para que el cliente sepa qué aplicación asociar con el archivo proporcionado. Y, debe configurar el Content-Length encabezado para que el cliente pueda calcular el progreso de la descarga, de lo contrario será desconocido. Y, debe configurar el Content-Disposition encabezado a attachment si quieres un Guardar como de lo contrario, el cliente intentará mostrarlo en línea. Finalmente, simplemente escriba el contenido del archivo en el flujo de salida de respuesta.

Aquí hay un ejemplo básico de tal servlet:

@WebServlet("/files/*")
public class FileServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        String filename = URLDecoder.decode(request.getPathInfo().substring(1), "UTF-8");
        File file = new File("/path/to/files", filename);
        response.setHeader("Content-Type", getServletContext().getMimeType(filename));
        response.setHeader("Content-Length", String.valueOf(file.length()));
        response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");
        Files.copy(file.toPath(), response.getOutputStream());
    }

}

Cuando se mapea en un url-pattern de por ejemplo /files/*entonces puedes llamarlo por http://example.com/files/image.png. De esta manera puede tener más control sobre las solicitudes que el DefaultServlet hace, como proporcionar una imagen predeterminada (es decir, if (!file.exists()) file = new File("/path/to/files", "404.gif") más o menos). También usando el request.getPathInfo() se prefiere arriba request.getParameter() porque es más compatible con SEO y, de lo contrario, IE no elegirá el nombre de archivo correcto durante Guardar como.

Puede reutilizar la misma lógica para servir archivos desde la base de datos. Simplemente reemplace new FileInputStream() por ResultSet#getInputStream().

Ver también:

  • Forma recomendada de guardar archivos cargados en una aplicación de servlet
  • Plantilla abstracta para un servlet de recursos estáticos (compatible con caché HTTP)
  • ¿Cómo recuperar y mostrar imágenes de una base de datos en una página JSP?
  • Cómo transmitir archivos de audio/video como MP3, MP4, AVI, etc. usando un Servlet

  • @SalutonMondo: el camino con el menor esfuerzo.

    – BalusC

    11/02/2015 a las 13:30

  • @BalusC, probé esto: <Context docBase="/path/to/images" path="/images" /> en Windows, pero obteniendo la ruta relativa a la carpeta de aplicaciones web: C:\install\apache-tomcat-8.0.26\webapps\tmp] is not valid

    – ACV

    12/09/2015 a las 16:50


  • En Windows debería ser: <Context docBase="C:\tmp\" path="/images" />

    – ACV

    12/09/2015 a las 16:52

  • Hay un par de problemas con esta respuesta, aunque responde de manera muy sucinta a la pregunta original. Estoy publicando esto con la esperanza de que se agregue una aclaración a la respuesta para que los lectores entiendan que esta no es “la solución”. Primero, este servlet agrega un error gigante de recorrido de ruta a cualquier aplicación en la que se implemente. Eso es lo más importante a mencionar.

    – Christopher Schultz

    3 de agosto de 2020 a las 3:18

  • @BalusC Sí, aprecio que sea posible encontrar una solución más segura y con más funciones a partir de esta respuesta. Simplemente creo que vale la pena poner un comentario de código en su ejemplo que diga algo como “TODO: MITIGAR DIRECTORY TRAVERSAL” o algo así. Desafortunadamente, SO es una gran fuente de soluciones para copiar y pegar en el software actual.

    – Christopher Schultz

    3 de agosto de 2020 a las 14:41

Avatar de usuario de Bozho
Bozho

Puede hacerlo colocando sus imágenes en una ruta fija (por ejemplo: /var/images, o c:\images), agregue una configuración en la configuración de su aplicación (representada en mi ejemplo por Settings.class) y cárguelas así, en un HttpServlet tuyo:

String filename = Settings.getValue("images.path") + request.getParameter("imageName")
FileInputStream fis = new FileInputStream(filename);

int b = 0;
while ((b = fis.read()) != -1) {
        response.getOutputStream().write(b);
}

O si quieres manipular la imagen:

String filename = Settings.getValue("images.path") + request.getParameter("imageName")
File imageFile = new File(filename);
BufferedImage image = ImageIO.read(imageFile);
ImageIO.write(image, "image/png", response.getOutputStream());

entonces el código html sería <img src="https://stackoverflow.com/questions/1812244/imageServlet?imageName=myimage.png" />

Por supuesto, debe pensar en ofrecer diferentes tipos de contenido: “imagen/jpeg”, por ejemplo, en función de la extensión del archivo. También debe proporcionar algo de almacenamiento en caché.

Además, podría usar este servlet para cambiar la escala de calidad de sus imágenes, proporcionando parámetros de ancho y alto como argumentos, y usando image.getScaledInstance(w, h, Image.SCALE_SMOOTH), teniendo en cuenta el rendimiento, por supuesto.

  • Realmente no necesita la API Java 2D para esto, solo agregaría innecesariamente más gastos generales. Simplemente lea un InputStream y escriba en OutputStream.

    – BalusC

    28 de noviembre de 2009 a las 11:44

  • Sí, comencé la respuesta con la idea de cambiar la escala y otras manipulaciones, pero terminé simplificándola.

    – Bozho

    28 de noviembre de 2009 a las 11:59

avatar de usuario de sbabamca
sbabamca

Requisito: acceder a los recursos estáticos (imágenes/videos, etc.) desde fuera del directorio WEBROOT o desde el disco local

Paso 1 :
Cree una carpeta en aplicaciones web del servidor tomcat. Digamos que el nombre de la carpeta es myproj

Paso 2 :
En myproj, cree una carpeta WEB-INF debajo de esto, cree un web.xml simple

código bajo web.xml

<web-app>
</web-app>

Estructura de directorio para los dos pasos anteriores

c:\programfile\apachesoftwarefoundation\tomcat\...\webapps
                                                            |
                                                            |---myproj
                                                            |   |
                                                            |   |---WEB-INF
                                                                |   |
                                                                    |---web.xml

Paso 3:
Ahora cree un archivo xml con el nombre myproj.xml en la siguiente ubicación

c:\programfile\apachesoftwarefoundation\tomcat\conf\catalina\localhost

CÓDIGO en myproj.xml:

<Context path="/myproj/images" docBase="e:/myproj/" crossContext="false" debug="0" reloadable="true" privileged="true" /> 

Etapa 4:
4 A) Ahora cree una carpeta con el nombre myproj en la unidad E de su disco duro y cree una nueva

carpeta con imágenes de nombre y coloque algunas imágenes en la carpeta de imágenes (e:myproj\images\)

Supongamos que myfoto.jpg se coloca debajo e:\myproj\images\myfoto.jpg

4 B) Ahora cree una carpeta con el nombre WEB-INF en e:\myproj\WEB-INF y crea un web.xml en la carpeta WEB-INF

Código en web.xml

<web-app>
</web-app>

Paso 5:
Ahora cree un documento .html con el nombre index.html y colóquelo en e:\myproj

CÓDIGO en index.html Bienvenido a Myproj

La estructura de directorios para los pasos 4 y 5 anteriores es la siguiente

E:\myproj
    |--index.html
    |
    |--images
    |     |----myfoto.jpg
    |
    |--WEB-INF
    |     |--web.xml

Paso 6:
Ahora inicie el servidor apache tomcat

Paso 7:
abra el navegador y escriba la url de la siguiente manera

http://localhost:8080/myproj    

luego muestra el contenido que se proporciona en index.html

Paso 8:
Para acceder a las imágenes en su disco duro local (fuera de webroot)

http://localhost:8080/myproj/images/myfoto.jpg

  • ¿Puede sugerirme cómo hacer lo mismo para los valores dinámicos? Quiero decir que quiero escribir los datos (xml) en mi directorio local o y leer eso en mi página jsp. ¿Hay alguna forma de escribir en el servidor administrado en el directorio para poder acceder a ellos utilizando el procedimiento anterior?

    – Sudip7

    21 de noviembre de 2014 a las 13:03

  • aunque puedo ejecutar el archivo index.html correctamente, pero no se muestran imágenes en el navegador web

    – roger guerra

    29 de mayo de 2015 a las 3:00

  • La publicación de mi error funciona bien. Solo olvidé poner / al final de E:/myproj. Cambio esto a E:/myproj/ y funciona bien. Gracias @sbabamca.

    – roger guerra

    29 de mayo de 2015 a las 3:08


  • Hola, gracias por el post y es muy útil. Aquí deseo cargar archivos a través de la interfaz a ese directorio específico. Deseo habilitar el método POST para el mismo. ¿Alguien puede ayudarme con lo mismo?

    – vicky

    13 de junio de 2015 a las 10:58


Añadir a servidor.xml:

 <Context docBase="c:/dirtoshare" path="/dir" />

Habilite el parámetro de listado de archivos dir en web.xml:

    <init-param>
        <param-name>listings</param-name>
        <param-value>true</param-value>
    </init-param>

Avatar de usuario de Raphaël Colantonio
Rafael Colantonio

Esta es la historia de mi lugar de trabajo:
– Intentamos cargar múltiples imágenes y archivos de documentos usando Struts 1 y Tomcat 7.x.
– Intentamos escribir los archivos cargados en el sistema de archivos, el nombre del archivo y la ruta completa a los registros de la base de datos.
– Tratamos de carpetas de archivos separadas afuera directorio de aplicaciones web.

La siguiente solución es bastante simple, efectiva para el requisito META-INF/context.xml : http://localhost:8080/ABCEn archivo ABCarchivo con el siguiente contenido: (Ejemplo, mi aplicación se ejecuta en context.xmlmi aplicación/proyecto llamado

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/ABC" aliases="/images=D:\images,/docs=D:\docs"/>

). (este también es el contenido completo del archivo

) (funciona con Tomcat versión 7 o posterior) D:\images\foo.jpg
Resultado:

<img src="http://localhost:8080/ABC/images/foo.jsp" alt="Foo" height="142" width="142">

Nos han creado 2 alias. Por ejemplo, guardamos imágenes en:

<img src="https://stackoverflow.com/images/foo.jsp" alt="Foo" height="142" width="142">

y ver desde el enlace o usando la etiqueta de imagen: WEB-INF\context.xmlo

(Uso Netbeans 7.x, Netbeans parece crear un archivo automáticamente
)

Avatar de usuario de Shantha Kumara FileServlet Shantha Kumara allowLinking="true" Si decide enviar a context.xml entonces también necesitarás FileServlet en

a fin de permitir para atravesar los enlaces simbólicos.

Ver
http://tomcat.apache.org/tomcat-6.0-doc/config/context.html

avatar de usuario de electrobabe electrobabe Si quieres trabajar con

@Path("/pic")
public Response get(@QueryParam("url") final String url) {
    String picUrl = URLDecoder.decode(url, "UTF-8");

    return Response.ok(sendPicAsStream(picUrl))
            .header(HttpHeaders.CONTENT_TYPE, "image/jpg")
            .build();
}

private StreamingOutput sendPicAsStream(String picUrl) {
    return output -> {
        try (InputStream is = (new URL(picUrl)).openStream()) {
            ByteStreams.copy(is, output);
        }
    };
}

JAX-RS javax.ws.rs.core.Response (por ejemplo, RESTEasy) intente esto: com.google.common.io.ByteStreams

¿Ha sido útil esta solución?