views:

546

answers:

2

I have a Java web application, using Spring and Struts, running on Tomcat 5.5. I want to load static images that will be shown both on the Web UI and in PDF files generated by the application. Also new images will be added and saved by uploading via the Web UI.

It's not a problem to do this by having the static data stored within the the web container but storing and loading them from outside the web container is giving me headache.

I'd prefer not to use a separate web server like Apache for serving the static data at this point. I also don't like the idea of storing the images in binary in a database.

I've seen some suggestions like having the image directory being a symbolic link pointing to a directory outside the web container, but will this approach work both on Windows and *nix environments?

Some suggest writing a filter or a servlet for handling the image serving but those suggestions have been very vague and high-level without pointers to more detailed information on how to accomplish this.

+1  A: 

You can do it by putting your images on a fixed path (for example: /var/images, or c:\images), add a setting in your application settings (represented in my example by the Settings.class), and load them like that, in a HttpServlet of yours:

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

Or if you want to manipulate the image:

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());

then the html code would be <img src="imageServlet?imageName=myimage.png" />

Of course you should think of serving different content types - "image/jpeg", for example based on the file extension. Also you should provide some caching.

In addition you could use this servlet for quality rescaling of your images, by providing width and height parameters as arguments, and using image.getScaledInstance(w, h, Image.SCALE_SMOOTH), considering performance, of course.

Bozho
You really don't need the Java 2D API for this, it would only unnecessarily add more overhead. Just read an InputStream and write to OutputStream.
BalusC
Yup, I started the response with the idea of rescaling and other manipulation, but ended up simplifying it.
Bozho
+3  A: 

I've seen some suggestions like having the image directory being a symbolic link pointing to a directory outside the web container, but will this approach work both on Windows and *nix environments?

If you adhere the *nix filesystem path rules (e.g. /path/to/images), it will work on Windows as well. It would however only be scanned on the same working disk as from where this command is been invoked. So if Tomcat is for example installed on C: then the path would actually point to C:/path/to/images.

If the images are all located outside the webapp and you want to have Tomcat's DefaultServlet to handle them, then all you basically need to do in Tomcat is to add the following Context element to /conf/server.xml:

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

This way they'll be accessible through http://example.com/images/....

If you want to have control over reading/writing images yourself, then you need to create a Servlet for this which basically just gets an InputStream of the image in flavor of for example FileInputStream and writes it to the OutputStream of the HttpServletResponse. Importantingly, you need to set the Content-Type response header as well and preferably also the Content-Length and Content-Disposition.

Here's a basic example of such a servlet:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
{
    String filename = URLDecoder.decode(request.getPathInfo(), "UTF-8");
    File file = new File("/path/to/images", filename);

    response.setContentType(getServletContext().getMimeType(file.getName()));
    response.setContentLength(file.length());
    response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");

    BufferedInputStream input = null;
    BufferedOutputStream output = null;

    try {
        input = new BufferedInputStream(new FileInputStream(file));
        output = new BufferedOutputStream(response.getOutputStream());

        byte[] buffer = new byte[8192];
        int length;
        while ((length = input.read(buffer)) > 0) {
            output.write(buffer, 0, length);
        }
    } finally {
        if (output != null) try { output.close(); } catch (IOException ignore) {}
        if (input != null) try { input.close(); } catch (IOException ignore) {}
    }
}

Map it on an url-pattern of for example /images/* and then you can call it by http://example.com/images/filename.jpg. This way you can have more control over the requests than the DefaultServlet does, such as providing a default image (i.e. if (!file.exists()) file = new File("/path/to/images", "404.gif") or so). Also using the request.getPathInfo() is preferred above request.getParameter() because it is more SEO friendly and otherwise IE won't pick the correct filename during Save As.

You can find here a complete example of a FileServlet supporting pretty much all of the efficientness which HTTP provides (caching, resume, gzip).

Hope this helps.

BalusC