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

131

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

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

Prefiero 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 apunta a un directorio fuera del contenedor web, pero ¿funcionará este enfoque en entornos Windows y * 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 punteros para obtener información más detallada sobre cómo lograr esto.

Janne
fuente

Respuestas:

161

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

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

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

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

De esta manera serán accesibles a través de http://example.com/files/.... 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 archivo Servletpara esto, que básicamente solo obtiene una InputStreammuestra del archivo, por ejemplo, FileInputStreamy lo escribe en OutputStreamel HttpServletResponse.

En la respuesta, debe establecer el Content-Typeencabezado para que el cliente sepa qué aplicación asociar con el archivo proporcionado. Y, debe configurar el Content-Lengthencabezado para que el cliente pueda calcular el progreso de la descarga, de lo contrario será desconocido. Y debe establecer el Content-Dispositionencabezado en attachmentsi desea un cuadro de diálogo Guardar como , de lo contrario, el cliente intentará mostrarlo en línea. Finalmente, solo escriba el contenido del archivo en la secuencia 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 asigna en un url-patternde, por ejemplo /files/*, puede llamarlo por http://example.com/files/image.png. De esta manera, puede tener más control sobre las solicitudes que las que DefaultServlettiene, como proporcionar una imagen predeterminada (es decir, más if (!file.exists()) file = new File("/path/to/files", "404.gif")o menos). También request.getPathInfo()se prefiere usar el anterior 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().

Espero que esto ayude.

Ver también:

BalusC
fuente
1
@SalutonMondo: el camino con menos esfuerzo.
BalusC
@BalusC, probé esto: <Context docBase="/path/to/images" path="/images" />en Windows, pero obteniendo la ruta relativa a la carpeta webapps:C:\install\apache-tomcat-8.0.26\webapps\tmp] is not valid
ACV
En Windows debería ser:<Context docBase="C:\tmp\" path="/images" />
ACV
Y obtener HTTP Status 404 - /images/al hacer: http://localhost:8080/images/PERO un archivo específico de debajo de las tmpobras: http://localhost:8080/images/Tulips.jpgestá bien
ACV
De esta manera, si pongo una imagen dentro de la carpeta, el enlace a la imagen dará la respuesta 404 a menos que reinicie el servidor, ¿hay alguna razón para esto?
Mateus Viccari
9

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 la clase Settings.class) y cárguelas así, en uno HttpServletde los tuyos:

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="imageServlet?imageName=myimage.png" />

Por supuesto, debe pensar en servir diferentes tipos de contenido: "image / jpeg", por ejemplo, según la extensión del archivo. También debe proporcionar un poco de almacenamiento en caché.

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

Bozho
fuente
2
Realmente no necesita la API Java 2D para esto, solo agregaría innecesariamente más sobrecarga. Simplemente lea un InputStream y escriba en OutputStream.
BalusC
1
Sí, comencé la respuesta con la idea de reescalar y otras manipulaciones, pero terminé simplificándola.
Bozho
7

Añadir a server.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>
cielo azul
fuente
Con el cambio en web.xml, ¿puedo obtener una lista de archivos para enviarlo al cuadro de selección?
Tomasz Waszczyk
1
este cambio se realizará en web.xml de tomcat, no en su aplicación
vsingh
¡Gracias por esto! ¿Es necesario habilitar el parámetro de listado de archivos dir en web.xml?
dariru
6

Requisito: Acceso 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 webapps 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 simple web.xml

código bajo web.xml

<web-app>
</web-app>

Estructura de directorios 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" /> 

Paso 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 colocar 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 e:\myproj\WEB-INFy cree 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 bajo 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
sbabamca
fuente
¿podría 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 y leerlos en mi página jsp. ¿Hay alguna forma de escribir en el servidor administrado en el directorio para que acceda a ellos usando el procedimiento anterior?
Sudip7
aunque puedo ejecutar el archivo index.html correctamente pero no se muestran imágenes en el navegador web
rogerwar
Mi mensaje de error está funcionando bien. Olvidé poner / al final de E: / myproj. Cambié esto a E: / myproj / y está funcionando bien. Gracias @sbabamca
rogerwar
Hola, gracias por el post y es muy útil. Aquí deseo subir archivos a través de la interfaz a ese directorio específico. Deseo habilitar el método POST para lo mismo. ¿Alguien puede ayudarme en lo mismo?
vicky
6

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 archivos cargados en el sistema de archivos, nombre de archivo y ruta completa a los registros de la base de datos.
- Intentamos separar las carpetas de archivos fuera del directorio de la aplicación web . (*)

La siguiente solución es bastante simple, efectiva para el requisito (*):

En un META-INF/context.xmlarchivo con el siguiente contenido: (Ejemplo, mi aplicación se ejecuta en http://localhost:8080/ABC, mi aplicación / proyecto nombrado ABC). (esto también es contenido completo del archivo context.xml)

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

(funciona con Tomcat versión 7 o posterior)

Resultado: hemos creado 2 alias. Por ejemplo, guardamos imágenes en: D:\images\foo.jpg y las vemos desde el enlace o usando la etiqueta de imagen:

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

o

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

(Uso Netbeans 7.x, Netbeans parece crear automáticamente el archivo WEB-INF\context.xml)

Do Nhu Vy
fuente
0

Si alguien no puede resolver su problema con la respuesta aceptada, tenga en cuenta las siguientes consideraciones:

  1. No es necesario mencionar localhost:<port>con <img> srcatributo.
  2. asegúrese de ejecutar este proyecto fuera de eclipse, porque eclipse crea una context docBaseentrada por sí mismo dentro de su server.xmlarchivo local .
JPG
fuente
0

Lea el InputStream de un archivo y escríbalo ServletOutputStreampara enviar datos binarios al cliente.

  • Archivo local Puede leer un archivo directamente usando FileInputStream ('ruta / imagen.png') .
  • Los archivos de Mongo DataBase pueden obtener InputStream usando GridFS .
@WebServlet("/files/URLStream")
public class URLStream extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public URLStream() {
        super();
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        File source = new File("D:\\SVN_Commit.PNG");
        long start = System.nanoTime();

        InputStream image = new FileInputStream(source);

        /*String fileID = request.getParameter("id");
        System.out.println("Requested File ID : "+fileID);
        // Mongo DB GridFS - https://stackoverflow.com/a/33544285/5081877
        image = outputImageFile.getInputStream();*/

        if( image != null ) {
            BufferedInputStream bin = null;
            BufferedOutputStream bout = null;
            ServletOutputStream sos = response.getOutputStream();
            try {
                bin = new BufferedInputStream( image );
                bout = new BufferedOutputStream( sos );
                int ch =0; ;
                while((ch=bin.read())!=-1) {
                    bout.write(ch);
                }
            } finally {
                bin.close();
                image.close();
                bout.close();
                sos.close();
            }

        } else {
            PrintWriter writer = response.getWriter();
            writer.append("Something went wrong with your request.");
            System.out.println("Image not available.");
        }
        System.out.println("Time taken by Stream Copy = "+(System.nanoTime()-start));
    }
}

Resultado de la URL directamente al srcatributo.

<img src='http://172.0.0.1:8080/ServletApp/files/URLStream?id=5a575be200c117cc2500003b' alt="mongodb File"/>
<img src='http://172.0.0.1:8080/ServletApp/files/URLStream' alt="local file"/>

<video controls="controls" src="http://172.0.0.1:8080/ServletApp/files/URLStream"></video>
Yash
fuente
0

Si desea trabajar con JAX-RS (por ejemplo, RESTEasy) intente esto:

@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);
        }
    };
}

usando javax.ws.rs.core.Responseycom.google.common.io.ByteStreams

electrobabe
fuente
-1

Lo hice aún más simple. Problema: un archivo CSS tenía enlaces de URL a la carpeta img. Obtiene 404.

Miré la url, http: // tomcatfolder: port / img / blablah.png , que no existe. Pero, eso realmente apunta a la aplicación ROOT en Tomcat.

Así que simplemente copié la carpeta img de mi aplicación web en esa aplicación ROOT. ¡Trabajos!

No recomendado para producción, por supuesto, pero esto es para una aplicación interna de desarrollo de herramientas.

Josef.B
fuente